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