source: josm/trunk/src/org/openstreetmap/josm/tools/XmlObjectParser.java@ 5580

Last change on this file since 5580 was 5580, checked in by Don-vip, 11 years ago

fix #8191 - Do not load external DTDs when reading (supposed) XML files. When reading W3C XHTML DTD (in case of invalid served XML file), this causes JOSM to hang two minutes.

  • Property svn:eol-style set to native
File size: 12.4 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.io.Reader;
8import java.lang.reflect.Field;
9import java.lang.reflect.Method;
10import java.lang.reflect.Modifier;
11import java.util.HashMap;
12import java.util.Iterator;
13import java.util.LinkedList;
14import java.util.Locale;
15import java.util.Map;
16import java.util.Stack;
17
18import javax.xml.parsers.ParserConfigurationException;
19import javax.xml.parsers.SAXParser;
20import javax.xml.parsers.SAXParserFactory;
21import javax.xml.transform.stream.StreamSource;
22import javax.xml.validation.Schema;
23import javax.xml.validation.SchemaFactory;
24import javax.xml.validation.ValidatorHandler;
25
26import org.openstreetmap.josm.io.MirroredInputStream;
27import org.xml.sax.Attributes;
28import org.xml.sax.ContentHandler;
29import org.xml.sax.InputSource;
30import org.xml.sax.Locator;
31import org.xml.sax.SAXException;
32import org.xml.sax.SAXNotRecognizedException;
33import org.xml.sax.SAXNotSupportedException;
34import org.xml.sax.SAXParseException;
35import org.xml.sax.XMLReader;
36import org.xml.sax.helpers.DefaultHandler;
37import org.xml.sax.helpers.XMLFilterImpl;
38
39/**
40 * An helper class that reads from a XML stream into specific objects.
41 *
42 * @author Imi
43 */
44public class XmlObjectParser implements Iterable<Object> {
45 public static class PresetParsingException extends SAXException {
46 private int columnNumber;
47 private int lineNumber;
48
49 public PresetParsingException() {
50 super();
51 }
52
53 public PresetParsingException(Exception e) {
54 super(e);
55 }
56
57 public PresetParsingException(String message, Exception e) {
58 super(message, e);
59 }
60
61 public PresetParsingException(String message) {
62 super(message);
63 }
64
65 public PresetParsingException rememberLocation(Locator locator) {
66 if (locator == null) return this;
67 this.columnNumber = locator.getColumnNumber();
68 this.lineNumber = locator.getLineNumber();
69 return this;
70 }
71
72 @Override
73 public String getMessage() {
74 String msg = super.getMessage();
75 if (lineNumber == 0 && columnNumber == 0)
76 return msg;
77 if (msg == null) {
78 msg = getClass().getName();
79 }
80 msg = msg + " " + tr("(at line {0}, column {1})", lineNumber, columnNumber);
81 return msg;
82 }
83
84 public int getColumnNumber() {
85 return columnNumber;
86 }
87
88 public int getLineNumber() {
89 return lineNumber;
90 }
91 }
92
93 public static final String lang = LanguageInfo.getLanguageCodeXML();
94
95 private static class AddNamespaceFilter extends XMLFilterImpl {
96
97 private final String namespace;
98
99 public AddNamespaceFilter(String namespace) {
100 this.namespace = namespace;
101 }
102
103 @Override
104 public void startElement (String uri, String localName, String qName, Attributes atts) throws SAXException {
105 if ("".equals(uri)) {
106 super.startElement(namespace, localName, qName, atts);
107 } else {
108 super.startElement(uri, localName, qName, atts);
109 }
110
111 }
112
113 }
114
115 private class Parser extends DefaultHandler {
116 Stack<Object> current = new Stack<Object>();
117 StringBuilder characters = new StringBuilder(64);
118
119 private Locator locator;
120
121 @Override
122 public void setDocumentLocator(Locator locator) {
123 this.locator = locator;
124 }
125
126 protected void throwException(Exception e) throws PresetParsingException{
127 throw new PresetParsingException(e).rememberLocation(locator);
128 }
129
130 @Override public void startElement(String ns, String lname, String qname, Attributes a) throws SAXException {
131 if (mapping.containsKey(qname)) {
132 Class<?> klass = mapping.get(qname).klass;
133 try {
134 current.push(klass.newInstance());
135 } catch (Exception e) {
136 throwException(e);
137 }
138 for (int i = 0; i < a.getLength(); ++i) {
139 setValue(mapping.get(qname), a.getQName(i), a.getValue(i));
140 }
141 if (mapping.get(qname).onStart) {
142 report();
143 }
144 if (mapping.get(qname).both) {
145 queue.add(current.peek());
146 }
147 }
148 }
149 @Override public void endElement(String ns, String lname, String qname) throws SAXException {
150 if (mapping.containsKey(qname) && !mapping.get(qname).onStart) {
151 report();
152 } else if (mapping.containsKey(qname) && characters != null && !current.isEmpty()) {
153 setValue(mapping.get(qname), qname, characters.toString().trim());
154 characters = new StringBuilder(64);
155 }
156 }
157 @Override public void characters(char[] ch, int start, int length) {
158 characters.append(ch, start, length);
159 }
160
161 private void report() {
162 queue.add(current.pop());
163 characters = new StringBuilder(64);
164 }
165
166 private Object getValueForClass(Class<?> klass, String value) {
167 if (klass == Boolean.TYPE)
168 return parseBoolean(value);
169 else if (klass == Integer.TYPE || klass == Long.TYPE)
170 return Long.parseLong(value);
171 else if (klass == Float.TYPE || klass == Double.TYPE)
172 return Double.parseDouble(value);
173 return value;
174 }
175
176 private void setValue(Entry entry, String fieldName, String value) throws SAXException {
177 if (entry == null) {
178 throw new NullPointerException("entry cannot be null");
179 }
180 if (fieldName.equals("class") || fieldName.equals("default") || fieldName.equals("throw") || fieldName.equals("new") || fieldName.equals("null")) {
181 fieldName += "_";
182 }
183 try {
184 Object c = current.peek();
185 Field f = entry.getField(fieldName);
186 if (f == null && fieldName.startsWith(lang)) {
187 f = entry.getField("locale_" + fieldName.substring(lang.length()));
188 }
189 if (f != null && Modifier.isPublic(f.getModifiers()) && String.class.equals(f.getType())) {
190 f.set(c, getValueForClass(f.getType(), value));
191 } else {
192 if (fieldName.startsWith(lang)) {
193 int l = lang.length();
194 fieldName = "set" + fieldName.substring(l, l + 1).toUpperCase(Locale.ENGLISH) + fieldName.substring(l + 1);
195 } else {
196 fieldName = "set" + fieldName.substring(0, 1).toUpperCase(Locale.ENGLISH) + fieldName.substring(1);
197 }
198 Method m = entry.getMethod(fieldName);
199 if (m != null) {
200 m.invoke(c, new Object[]{getValueForClass(m.getParameterTypes()[0], value)});
201 }
202 }
203 } catch (Exception e) {
204 e.printStackTrace(); // SAXException does not dump inner exceptions.
205 throwException(e);
206 }
207 }
208
209 private boolean parseBoolean(String s) {
210 return s != null
211 && !s.equals("0")
212 && !s.startsWith("off")
213 && !s.startsWith("false")
214 && !s.startsWith("no");
215 }
216
217 @Override
218 public void error(SAXParseException e) throws SAXException {
219 throwException(e);
220 }
221
222 @Override
223 public void fatalError(SAXParseException e) throws SAXException {
224 throwException(e);
225 }
226 }
227
228 private static class Entry {
229 Class<?> klass;
230 boolean onStart;
231 boolean both;
232 private final Map<String, Field> fields = new HashMap<String, Field>();
233 private final Map<String, Method> methods = new HashMap<String, Method>();
234
235 public Entry(Class<?> klass, boolean onStart, boolean both) {
236 this.klass = klass;
237 this.onStart = onStart;
238 this.both = both;
239 }
240
241 Field getField(String s) {
242 if (fields.containsKey(s)) {
243 return fields.get(s);
244 } else {
245 try {
246 Field f = klass.getField(s);
247 fields.put(s, f);
248 return f;
249 } catch (NoSuchFieldException ex) {
250 fields.put(s, null);
251 return null;
252 }
253 }
254 }
255
256 Method getMethod(String s) {
257 if (methods.containsKey(s)) {
258 return methods.get(s);
259 } else {
260 for (Method m : klass.getMethods()) {
261 if (m.getName().equals(s) && m.getParameterTypes().length == 1) {
262 methods.put(s, m);
263 return m;
264 }
265 }
266 methods.put(s, null);
267 return null;
268 }
269 }
270 }
271
272 private Map<String, Entry> mapping = new HashMap<String, Entry>();
273 private DefaultHandler parser;
274
275 /**
276 * The queue of already parsed items from the parsing thread.
277 */
278 private LinkedList<Object> queue = new LinkedList<Object>();
279 private Iterator<Object> queueIterator = null;
280
281 public XmlObjectParser() {
282 parser = new Parser();
283 }
284
285 public XmlObjectParser(DefaultHandler handler) {
286 parser = handler;
287 }
288
289 private Iterable<Object> start(final Reader in, final ContentHandler contentHandler) throws SAXException, IOException {
290 try {
291 SAXParserFactory parserFactory = SAXParserFactory.newInstance();
292 parserFactory.setNamespaceAware(true);
293 SAXParser saxParser = parserFactory.newSAXParser();
294 XMLReader reader = saxParser.getXMLReader();
295 reader.setContentHandler(contentHandler);
296 try {
297 // Do not load external DTDs (fix #8191)
298 reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
299 } catch (SAXException e) {
300 // Exception very unlikely to happen, so no need to translate this
301 System.err.println("Cannot disable 'load-external-dtd' feature: "+e.getMessage());
302 }
303 reader.parse(new InputSource(in));
304 queueIterator = queue.iterator();
305 return this;
306 } catch (ParserConfigurationException e) {
307 // This should never happen ;-)
308 throw new RuntimeException(e);
309 }
310 }
311
312 public Iterable<Object> start(final Reader in) throws SAXException {
313 try {
314 return start(in, parser);
315 } catch (IOException e) {
316 throw new SAXException(e);
317 }
318 }
319
320 public Iterable<Object> startWithValidation(final Reader in, String namespace, String schemaSource) throws SAXException {
321 try {
322 SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
323 Schema schema = factory.newSchema(new StreamSource(new MirroredInputStream(schemaSource)));
324 ValidatorHandler validator = schema.newValidatorHandler();
325 validator.setContentHandler(parser);
326 validator.setErrorHandler(parser);
327
328 AddNamespaceFilter filter = new AddNamespaceFilter(namespace);
329 filter.setContentHandler(validator);
330 return start(in, filter);
331 } catch(IOException e) {
332 throw new SAXException(tr("Failed to load XML schema."), e);
333 }
334 }
335
336 public void map(String tagName, Class<?> klass) {
337 mapping.put(tagName, new Entry(klass,false,false));
338 }
339
340 public void mapOnStart(String tagName, Class<?> klass) {
341 mapping.put(tagName, new Entry(klass,true,false));
342 }
343
344 public void mapBoth(String tagName, Class<?> klass) {
345 mapping.put(tagName, new Entry(klass,false,true));
346 }
347
348 public Object next() {
349 return queueIterator.next();
350 }
351
352 public boolean hasNext() {
353 return queueIterator.hasNext();
354 }
355
356 @Override
357 public Iterator<Object> iterator() {
358 return queue.iterator();
359 }
360}
Note: See TracBrowser for help on using the repository browser.