source: josm/trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java@ 8287

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

fix findsecbugs:XXE_SAXPARSER - "Security - XML Parsing Vulnerable to XXE (SAXParser)"

  • Property svn:eol-style set to native
File size: 11.3 KB
RevLine 
[4240]1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.imagery;
3
4import java.io.IOException;
5import java.io.InputStream;
6import java.util.ArrayList;
7import java.util.Arrays;
8import java.util.List;
[7083]9import java.util.Objects;
[4240]10import java.util.Stack;
11
12import javax.xml.parsers.ParserConfigurationException;
13
[6643]14import org.openstreetmap.josm.Main;
[4240]15import org.openstreetmap.josm.data.imagery.ImageryInfo;
[4423]16import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
[4240]17import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
[4423]18import org.openstreetmap.josm.data.imagery.Shape;
[7248]19import org.openstreetmap.josm.io.CachedFile;
[4240]20import org.openstreetmap.josm.io.UTFInputStreamReader;
[8091]21import org.openstreetmap.josm.tools.LanguageInfo;
[8287]22import org.openstreetmap.josm.tools.Utils;
[4240]23import org.xml.sax.Attributes;
24import org.xml.sax.InputSource;
25import org.xml.sax.SAXException;
26import org.xml.sax.helpers.DefaultHandler;
27
28public class ImageryReader {
29
30 private String source;
31
[4874]32 private enum State {
[4240]33 INIT, // initial state, should always be at the bottom of the stack
34 IMAGERY, // inside the imagery element
35 ENTRY, // inside an entry
36 ENTRY_ATTRIBUTE, // note we are inside an entry attribute to collect the character data
[4439]37 PROJECTIONS,
38 CODE,
[4423]39 BOUNDS,
40 SHAPE,
[4240]41 UNKNOWN, // element is not recognized in the current context
42 }
43
[4423]44 public ImageryReader(String source) {
[4240]45 this.source = source;
46 }
47
48 public List<ImageryInfo> parse() throws SAXException, IOException {
[4430]49 Parser parser = new Parser();
[4240]50 try {
[7248]51 try (InputStream in = new CachedFile(source)
52 .setMaxAge(1*CachedFile.DAYS)
53 .setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince)
54 .getInputStream()) {
[7033]55 InputSource is = new InputSource(UTFInputStreamReader.create(in));
[8287]56 Utils.newSafeSAXParser().parse(is, parser);
[7033]57 return parser.entries;
58 }
[4430]59 } catch (SAXException e) {
60 throw e;
61 } catch (ParserConfigurationException e) {
[6643]62 Main.error(e); // broken SAXException chaining
[4430]63 throw new SAXException(e);
[4240]64 }
65 }
66
[4874]67 private static class Parser extends DefaultHandler {
[4240]68 private StringBuffer accumulator = new StringBuffer();
69
70 private Stack<State> states;
71
[8285]72 private List<ImageryInfo> entries;
[4240]73
74 /**
75 * Skip the current entry because it has mandatory attributes
76 * that this version of JOSM cannot process.
77 */
[8285]78 private boolean skipEntry;
[4240]79
[8285]80 private ImageryInfo entry;
81 private ImageryBounds bounds;
82 private Shape shape;
[8091]83 // language of last element, does only work for simple ENTRY_ATTRIBUTE's
[8285]84 private String lang;
85 private List<String> projections;
[4240]86
[8285]87 @Override
88 public void startDocument() {
[4240]89 accumulator = new StringBuffer();
90 skipEntry = false;
[7005]91 states = new Stack<>();
[4240]92 states.push(State.INIT);
[7005]93 entries = new ArrayList<>();
[4240]94 entry = null;
95 bounds = null;
[4439]96 projections = null;
[4240]97 }
98
99 @Override
100 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
101 accumulator.setLength(0);
102 State newState = null;
103 switch (states.peek()) {
[4874]104 case INIT:
[7012]105 if ("imagery".equals(qName)) {
[4874]106 newState = State.IMAGERY;
107 }
108 break;
109 case IMAGERY:
[7012]110 if ("entry".equals(qName)) {
[4874]111 entry = new ImageryInfo();
112 skipEntry = false;
113 newState = State.ENTRY;
114 }
115 break;
116 case ENTRY:
117 if (Arrays.asList(new String[] {
[4240]118 "name",
[7186]119 "id",
[4240]120 "type",
[8065]121 "description",
[4240]122 "default",
123 "url",
124 "eula",
125 "min-zoom",
126 "max-zoom",
127 "attribution-text",
128 "attribution-url",
129 "logo-image",
130 "logo-url",
131 "terms-of-use-text",
132 "terms-of-use-url",
[4405]133 "country-code",
[4713]134 "icon",
[4874]135 }).contains(qName)) {
136 newState = State.ENTRY_ATTRIBUTE;
[8091]137 lang = atts.getValue("lang");
[7012]138 } else if ("bounds".equals(qName)) {
[4874]139 try {
140 bounds = new ImageryBounds(
141 atts.getValue("min-lat") + "," +
142 atts.getValue("min-lon") + "," +
143 atts.getValue("max-lat") + "," +
144 atts.getValue("max-lon"), ",");
145 } catch (IllegalArgumentException e) {
146 break;
[4240]147 }
[4874]148 newState = State.BOUNDS;
[7012]149 } else if ("projections".equals(qName)) {
[7005]150 projections = new ArrayList<>();
[4874]151 newState = State.PROJECTIONS;
152 }
153 break;
154 case BOUNDS:
[7012]155 if ("shape".equals(qName)) {
[4874]156 shape = new Shape();
157 newState = State.SHAPE;
158 }
159 break;
160 case SHAPE:
[7012]161 if ("point".equals(qName)) {
[4874]162 try {
163 shape.addPoint(atts.getValue("lat"), atts.getValue("lon"));
164 } catch (IllegalArgumentException e) {
165 break;
[4423]166 }
[4874]167 }
168 break;
169 case PROJECTIONS:
[7012]170 if ("code".equals(qName)) {
[4874]171 newState = State.CODE;
172 }
173 break;
[4240]174 }
175 /**
[4310]176 * Did not recognize the element, so the new state is UNKNOWN.
[4240]177 * This includes the case where we are already inside an unknown
178 * element, i.e. we do not try to understand the inner content
179 * of an unknown element, but wait till it's over.
180 */
181 if (newState == null) {
182 newState = State.UNKNOWN;
183 }
184 states.push(newState);
[7083]185 if (newState == State.UNKNOWN && "true".equals(atts.getValue("mandatory"))) {
[4240]186 skipEntry = true;
187 }
188 return;
189 }
190
191 @Override
192 public void characters(char[] ch, int start, int length) {
193 accumulator.append(ch, start, length);
194 }
195
196 @Override
197 public void endElement(String namespaceURI, String qName, String rqName) {
198 switch (states.pop()) {
[4874]199 case INIT:
200 throw new RuntimeException("parsing error: more closing than opening elements");
201 case ENTRY:
[7012]202 if ("entry".equals(qName)) {
[4874]203 if (!skipEntry) {
204 entries.add(entry);
205 }
206 entry = null;
207 }
208 break;
209 case ENTRY_ATTRIBUTE:
[7012]210 switch(qName) {
211 case "name":
[8091]212 entry.setName(lang == null ? LanguageInfo.getJOSMLocaleCode(null) : lang, accumulator.toString());
[7012]213 break;
[8065]214 case "description":
[8091]215 entry.setDescription(lang, accumulator.toString());
[8065]216 break;
[7186]217 case "id":
218 entry.setId(accumulator.toString());
219 break;
[7012]220 case "type":
[4874]221 boolean found = false;
222 for (ImageryType type : ImageryType.values()) {
[7083]223 if (Objects.equals(accumulator.toString(), type.getTypeString())) {
[4874]224 entry.setImageryType(type);
225 found = true;
226 break;
[4240]227 }
228 }
[4874]229 if (!found) {
230 skipEntry = true;
231 }
[7012]232 break;
233 case "default":
234 switch (accumulator.toString()) {
235 case "true":
[4874]236 entry.setDefaultEntry(true);
[7012]237 break;
238 case "false":
[4874]239 entry.setDefaultEntry(false);
[7012]240 break;
241 default:
[4874]242 skipEntry = true;
243 }
[7012]244 break;
245 case "url":
[4874]246 entry.setUrl(accumulator.toString());
[7012]247 break;
248 case "eula":
[4874]249 entry.setEulaAcceptanceRequired(accumulator.toString());
[7012]250 break;
251 case "min-zoom":
252 case "max-zoom":
[4874]253 Integer val = null;
254 try {
255 val = Integer.parseInt(accumulator.toString());
256 } catch(NumberFormatException e) {
257 val = null;
258 }
259 if (val == null) {
260 skipEntry = true;
261 } else {
[7012]262 if ("min-zoom".equals(qName)) {
[4874]263 entry.setDefaultMinZoom(val);
[4240]264 } else {
[4874]265 entry.setDefaultMaxZoom(val);
[4240]266 }
267 }
[7012]268 break;
269 case "attribution-text":
[4874]270 entry.setAttributionText(accumulator.toString());
[7012]271 break;
272 case "attribution-url":
[4874]273 entry.setAttributionLinkURL(accumulator.toString());
[7012]274 break;
275 case "logo-image":
[4874]276 entry.setAttributionImage(accumulator.toString());
[7012]277 break;
278 case "logo-url":
[4874]279 entry.setAttributionImageURL(accumulator.toString());
[7012]280 break;
281 case "terms-of-use-text":
[4874]282 entry.setTermsOfUseText(accumulator.toString());
[7012]283 break;
284 case "terms-of-use-url":
[4874]285 entry.setTermsOfUseURL(accumulator.toString());
[7012]286 break;
287 case "country-code":
[4874]288 entry.setCountryCode(accumulator.toString());
[7012]289 break;
290 case "icon":
[4874]291 entry.setIcon(accumulator.toString());
[7012]292 break;
[4874]293 }
294 break;
295 case BOUNDS:
296 entry.setBounds(bounds);
297 bounds = null;
298 break;
299 case SHAPE:
300 bounds.addShape(shape);
301 shape = null;
302 break;
303 case CODE:
304 projections.add(accumulator.toString());
305 break;
306 case PROJECTIONS:
307 entry.setServerProjections(projections);
308 projections = null;
309 break;
[4240]310 }
311 }
312 }
313}
Note: See TracBrowser for help on using the repository browser.