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 17package libcore.xml; 18 19import java.io.StringReader; 20import java.util.Arrays; 21import java.util.List; 22import javax.xml.parsers.SAXParser; 23import javax.xml.parsers.SAXParserFactory; 24import junit.framework.AssertionFailedError; 25import junit.framework.TestCase; 26import org.xml.sax.Attributes; 27import org.xml.sax.ContentHandler; 28import org.xml.sax.InputSource; 29import org.xml.sax.SAXParseException; 30import org.xml.sax.XMLReader; 31import org.xml.sax.helpers.DefaultHandler; 32 33/** 34 * Initiate and observe a SAX parse session. 35 */ 36public final class SaxTest extends TestCase { 37 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 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 */ 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 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 */ 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 */ 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 */ 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 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 */ 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 { 229 @Override public InputSource resolveEntity(String publicId, String systemId) { 230 throw new AssertionFailedError(); 231 } 232 @Override public void notationDecl(String name, String publicId, String systemId) { 233 throw new AssertionFailedError(); 234 } 235 @Override public void unparsedEntityDecl( 236 String name, String publicId, String systemId, String notationName) { 237 throw new AssertionFailedError(); 238 } 239 @Override public void startPrefixMapping(String prefix, String uri) { 240 throw new AssertionFailedError(); 241 } 242 @Override public void endPrefixMapping(String prefix) { 243 throw new AssertionFailedError(); 244 } 245 @Override public void startElement( 246 String uri, String localName, String qName, Attributes attributes) { 247 throw new AssertionFailedError(); 248 } 249 @Override public void endElement(String uri, String localName, String qName) { 250 throw new AssertionFailedError(); 251 } 252 @Override public void characters(char[] ch, int start, int length) { 253 throw new AssertionFailedError(); 254 } 255 @Override public void ignorableWhitespace(char[] ch, int start, int length) { 256 throw new AssertionFailedError(); 257 } 258 @Override public void processingInstruction(String target, String data) { 259 throw new AssertionFailedError(); 260 } 261 @Override public void skippedEntity(String name) { 262 throw new AssertionFailedError(); 263 } 264 @Override public void warning(SAXParseException e) { 265 throw new AssertionFailedError(); 266 } 267 @Override public void error(SAXParseException e) { 268 throw new AssertionFailedError(); 269 } 270 @Override public void fatalError(SAXParseException e) { 271 throw new AssertionFailedError(); 272 } 273 } 274} 275