1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package libcore.xml;
18 
19 import java.io.StringReader;
20 import java.util.Arrays;
21 import java.util.List;
22 import javax.xml.parsers.SAXParser;
23 import javax.xml.parsers.SAXParserFactory;
24 import junit.framework.AssertionFailedError;
25 import junit.framework.TestCase;
26 import org.xml.sax.Attributes;
27 import org.xml.sax.ContentHandler;
28 import org.xml.sax.InputSource;
29 import org.xml.sax.SAXParseException;
30 import org.xml.sax.XMLReader;
31 import org.xml.sax.helpers.DefaultHandler;
32 
33 /**
34  * Initiate and observe a SAX parse session.
35  */
36 public final class SaxTest extends TestCase {
37 
testNoPrefixesNoNamespaces()38     public void testNoPrefixesNoNamespaces() throws Exception {
39         parse(false, false, "<foo bar=\"baz\"/>", new DefaultHandler() {
40             @Override public void startElement(String uri, String localName,
41                     String qName, Attributes attributes) {
42                 assertEquals("", uri);
43                 assertEquals("", localName);
44                 assertEquals("foo", qName);
45                 assertEquals(1, attributes.getLength());
46                 assertEquals("", attributes.getURI(0));
47                 assertOneOf("bar", "", attributes.getLocalName(0));
48                 assertEquals("bar", attributes.getQName(0));
49             }
50         });
51 
52         parse(false, false, "<a:foo a:bar=\"baz\"/>", new DefaultHandler() {
53             @Override public void startElement(String uri, String localName,
54                     String qName, Attributes attributes) {
55                 assertEquals("", uri);
56                 assertEquals("", localName);
57                 assertEquals("a:foo", qName);
58                 assertEquals(1, attributes.getLength());
59                 assertEquals("", attributes.getURI(0));
60                 assertOneOf("a:bar", "", attributes.getLocalName(0));
61                 assertEquals("a:bar", attributes.getQName(0));
62             }
63         });
64     }
65 
testNoPrefixesYesNamespaces()66     public void testNoPrefixesYesNamespaces() throws Exception {
67         parse(false, true, "<foo bar=\"baz\"/>", new DefaultHandler() {
68             @Override public void startElement(String uri, String localName,
69                     String qName, Attributes attributes) {
70                 assertEquals("", uri);
71                 assertEquals("foo", localName);
72                 assertEquals("foo", qName);
73                 assertEquals(1, attributes.getLength());
74                 assertEquals("", attributes.getURI(0));
75                 assertEquals("bar", attributes.getLocalName(0));
76                 assertEquals("bar", attributes.getQName(0));
77             }
78         });
79 
80         parse(false, true, "<a:foo a:bar=\"baz\" xmlns:a=\"http://quux\"/>", new DefaultHandler() {
81             @Override public void startElement(String uri, String localName,
82                     String qName, Attributes attributes) {
83                 assertEquals("http://quux", uri);
84                 assertEquals("foo", localName);
85                 assertEquals("a:foo", qName);
86                 assertEquals(1, attributes.getLength());
87                 assertEquals("http://quux", attributes.getURI(0));
88                 assertEquals("bar", attributes.getLocalName(0));
89                 assertEquals("a:bar", attributes.getQName(0));
90             }
91         });
92     }
93 
94     /**
95      * Android's Expat-based SAX parser fails this test because Expat doesn't
96      * supply us with our much desired {@code xmlns="http://..."} attributes.
97      */
testYesPrefixesYesNamespaces()98     public void testYesPrefixesYesNamespaces() throws Exception {
99         parse(true, true, "<foo bar=\"baz\"/>", new DefaultHandler() {
100             @Override public void startElement(String uri, String localName,
101                     String qName, Attributes attributes) {
102                 assertEquals("", uri);
103                 assertEquals("foo", localName);
104                 assertEquals("foo", qName);
105                 assertEquals(1, attributes.getLength());
106                 assertEquals("", attributes.getURI(0));
107                 assertEquals("bar", attributes.getLocalName(0));
108                 assertEquals("bar", attributes.getQName(0));
109             }
110         });
111 
112         parse(true, true, "<a:foo a:bar=\"baz\" xmlns:a=\"http://quux\"/>", new DefaultHandler() {
113             @Override public void startElement(String uri, String localName,
114                     String qName, Attributes attributes) {
115                 assertEquals("http://quux", uri);
116                 assertEquals("foo", localName);
117                 assertEquals("a:foo", qName);
118                 assertEquals(2, attributes.getLength());
119                 assertEquals("http://quux", attributes.getURI(0));
120                 assertEquals("bar", attributes.getLocalName(0));
121                 assertEquals("a:bar", attributes.getQName(0));
122                 assertEquals("", attributes.getURI(1));
123                 assertEquals("", attributes.getLocalName(1));
124                 assertEquals("xmlns:a", attributes.getQName(1));
125             }
126         });
127     }
128 
testYesPrefixesNoNamespaces()129     public void testYesPrefixesNoNamespaces() throws Exception {
130         parse(true, false, "<foo bar=\"baz\"/>", new DefaultHandler() {
131             @Override public void startElement(String uri, String localName,
132                     String qName, Attributes attributes) {
133                 assertEquals("", uri);
134                 assertEquals("", localName);
135                 assertEquals("foo", qName);
136                 assertEquals(1, attributes.getLength());
137                 assertEquals("", attributes.getURI(0));
138                 assertOneOf("bar", "", attributes.getLocalName(0));
139                 assertEquals("bar", attributes.getQName(0));
140             }
141         });
142 
143         parse(true, false, "<a:foo a:bar=\"baz\"/>", new DefaultHandler() {
144             @Override public void startElement(String uri, String localName,
145                     String qName, Attributes attributes) {
146                 assertEquals("", uri);
147                 assertEquals("", localName);
148                 assertEquals("a:foo", qName);
149                 assertEquals(1, attributes.getLength());
150                 assertEquals("", attributes.getURI(0));
151                 assertOneOf("a:bar", "", attributes.getLocalName(0));
152                 assertEquals("a:bar", attributes.getQName(0));
153             }
154         });
155     }
156 
157     /**
158      * Test that the external-general-entities feature can be disabled.
159      * http://code.google.com/p/android/issues/detail?id=9493
160      */
testDisableExternalGeneralEntities()161     public void testDisableExternalGeneralEntities() throws Exception {
162         String xml = "<!DOCTYPE foo ["
163                 + "  <!ENTITY bar SYSTEM \"/no-such-document.xml\">"
164                 + "]>"
165                 + "<foo>&bar;</foo>";
166         testDisableExternalEntities("http://xml.org/sax/features/external-general-entities", xml);
167     }
168 
169     /**
170      * Test that the external-parameter-entities feature can be disabled.
171      * http://code.google.com/p/android/issues/detail?id=9493
172      */
testDisableExternalParameterEntities()173     public void testDisableExternalParameterEntities() throws Exception {
174         String xml = "<!DOCTYPE foo ["
175                 + "  <!ENTITY % bar SYSTEM \"/no-such-document.xml\">"
176                 + "  %bar;"
177                 + "]>"
178                 + "<foo/>";
179         testDisableExternalEntities("http://xml.org/sax/features/external-parameter-entities", xml);
180     }
181 
182     /**
183      * Disables the named feature and then parses the supplied XML. The content
184      * is expected to be equivalent to "<foo/>".
185      */
testDisableExternalEntities(String feature, String xml)186     private void testDisableExternalEntities(String feature, String xml) throws Exception {
187         SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
188         XMLReader reader = parser.getXMLReader();
189         reader.setFeature(feature, false);
190         assertFalse(reader.getFeature(feature));
191         reader.setContentHandler(new ThrowingHandler() {
192             @Override public void startElement(
193                     String uri, String localName, String qName, Attributes attributes) {
194                 assertEquals("foo", qName);
195             }
196             @Override public void endElement(String uri, String localName, String qName) {
197                 assertEquals("foo", qName);
198             }
199         });
200         reader.parse(new InputSource(new StringReader(xml)));
201     }
202 
parse(boolean prefixes, boolean namespaces, String xml, ContentHandler handler)203     private void parse(boolean prefixes, boolean namespaces, String xml,
204             ContentHandler handler) throws Exception {
205         SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
206         XMLReader reader = parser.getXMLReader();
207         reader.setFeature("http://xml.org/sax/features/namespace-prefixes", prefixes);
208         reader.setFeature("http://xml.org/sax/features/namespaces", namespaces);
209         reader.setContentHandler(handler);
210         reader.parse(new InputSource(new StringReader(xml)));
211     }
212 
213     /**
214      * @param expected an optional value that may or may have not been supplied
215      * @param sentinel a marker value that means the expected value was omitted
216      */
assertOneOf(String expected, String sentinel, String actual)217     private void assertOneOf(String expected, String sentinel, String actual) {
218         List<String> optionsList = Arrays.asList(sentinel, expected);
219         assertTrue("Expected one of " + optionsList + " but was " + actual,
220                 optionsList.contains(actual));
221     }
222 
223     /**
224      * This SAX handler throws on everything but startDocument, endDocument,
225      * and setDocumentLocator(). Override the methods that are expected to be
226      * called.
227      */
228     static class ThrowingHandler extends DefaultHandler {
resolveEntity(String publicId, String systemId)229         @Override public InputSource resolveEntity(String publicId, String systemId) {
230             throw new AssertionFailedError();
231         }
notationDecl(String name, String publicId, String systemId)232         @Override public void notationDecl(String name, String publicId, String systemId) {
233             throw new AssertionFailedError();
234         }
unparsedEntityDecl( String name, String publicId, String systemId, String notationName)235         @Override public void unparsedEntityDecl(
236                 String name, String publicId, String systemId, String notationName) {
237             throw new AssertionFailedError();
238         }
startPrefixMapping(String prefix, String uri)239         @Override public void startPrefixMapping(String prefix, String uri) {
240             throw new AssertionFailedError();
241         }
endPrefixMapping(String prefix)242         @Override public void endPrefixMapping(String prefix) {
243             throw new AssertionFailedError();
244         }
startElement( String uri, String localName, String qName, Attributes attributes)245         @Override public void startElement(
246                 String uri, String localName, String qName, Attributes attributes) {
247             throw new AssertionFailedError();
248         }
endElement(String uri, String localName, String qName)249         @Override public void endElement(String uri, String localName, String qName) {
250             throw new AssertionFailedError();
251         }
characters(char[] ch, int start, int length)252         @Override public void characters(char[] ch, int start, int length) {
253             throw new AssertionFailedError();
254         }
ignorableWhitespace(char[] ch, int start, int length)255         @Override public void ignorableWhitespace(char[] ch, int start, int length) {
256             throw new AssertionFailedError();
257         }
processingInstruction(String target, String data)258         @Override public void processingInstruction(String target, String data) {
259             throw new AssertionFailedError();
260         }
skippedEntity(String name)261         @Override public void skippedEntity(String name) {
262             throw new AssertionFailedError();
263         }
warning(SAXParseException e)264         @Override public void warning(SAXParseException e) {
265             throw new AssertionFailedError();
266         }
error(SAXParseException e)267         @Override public void error(SAXParseException e) {
268             throw new AssertionFailedError();
269         }
fatalError(SAXParseException e)270         @Override public void fatalError(SAXParseException e) {
271             throw new AssertionFailedError();
272         }
273     }
274 }
275