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
Line 
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;
9import java.util.Objects;
10import java.util.Stack;
11
12import javax.xml.parsers.ParserConfigurationException;
13
14import org.openstreetmap.josm.Main;
15import org.openstreetmap.josm.data.imagery.ImageryInfo;
16import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
17import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
18import org.openstreetmap.josm.data.imagery.Shape;
19import org.openstreetmap.josm.io.CachedFile;
20import org.openstreetmap.josm.io.UTFInputStreamReader;
21import org.openstreetmap.josm.tools.LanguageInfo;
22import org.openstreetmap.josm.tools.Utils;
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
32 private enum State {
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
37 PROJECTIONS,
38 CODE,
39 BOUNDS,
40 SHAPE,
41 UNKNOWN, // element is not recognized in the current context
42 }
43
44 public ImageryReader(String source) {
45 this.source = source;
46 }
47
48 public List<ImageryInfo> parse() throws SAXException, IOException {
49 Parser parser = new Parser();
50 try {
51 try (InputStream in = new CachedFile(source)
52 .setMaxAge(1*CachedFile.DAYS)
53 .setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince)
54 .getInputStream()) {
55 InputSource is = new InputSource(UTFInputStreamReader.create(in));
56 Utils.newSafeSAXParser().parse(is, parser);
57 return parser.entries;
58 }
59 } catch (SAXException e) {
60 throw e;
61 } catch (ParserConfigurationException e) {
62 Main.error(e); // broken SAXException chaining
63 throw new SAXException(e);
64 }
65 }
66
67 private static class Parser extends DefaultHandler {
68 private StringBuffer accumulator = new StringBuffer();
69
70 private Stack<State> states;
71
72 private List<ImageryInfo> entries;
73
74 /**
75 * Skip the current entry because it has mandatory attributes
76 * that this version of JOSM cannot process.
77 */
78 private boolean skipEntry;
79
80 private ImageryInfo entry;
81 private ImageryBounds bounds;
82 private Shape shape;
83 // language of last element, does only work for simple ENTRY_ATTRIBUTE's
84 private String lang;
85 private List<String> projections;
86
87 @Override
88 public void startDocument() {
89 accumulator = new StringBuffer();
90 skipEntry = false;
91 states = new Stack<>();
92 states.push(State.INIT);
93 entries = new ArrayList<>();
94 entry = null;
95 bounds = null;
96 projections = null;
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()) {
104 case INIT:
105 if ("imagery".equals(qName)) {
106 newState = State.IMAGERY;
107 }
108 break;
109 case IMAGERY:
110 if ("entry".equals(qName)) {
111 entry = new ImageryInfo();
112 skipEntry = false;
113 newState = State.ENTRY;
114 }
115 break;
116 case ENTRY:
117 if (Arrays.asList(new String[] {
118 "name",
119 "id",
120 "type",
121 "description",
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",
133 "country-code",
134 "icon",
135 }).contains(qName)) {
136 newState = State.ENTRY_ATTRIBUTE;
137 lang = atts.getValue("lang");
138 } else if ("bounds".equals(qName)) {
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;
147 }
148 newState = State.BOUNDS;
149 } else if ("projections".equals(qName)) {
150 projections = new ArrayList<>();
151 newState = State.PROJECTIONS;
152 }
153 break;
154 case BOUNDS:
155 if ("shape".equals(qName)) {
156 shape = new Shape();
157 newState = State.SHAPE;
158 }
159 break;
160 case SHAPE:
161 if ("point".equals(qName)) {
162 try {
163 shape.addPoint(atts.getValue("lat"), atts.getValue("lon"));
164 } catch (IllegalArgumentException e) {
165 break;
166 }
167 }
168 break;
169 case PROJECTIONS:
170 if ("code".equals(qName)) {
171 newState = State.CODE;
172 }
173 break;
174 }
175 /**
176 * Did not recognize the element, so the new state is UNKNOWN.
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);
185 if (newState == State.UNKNOWN && "true".equals(atts.getValue("mandatory"))) {
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()) {
199 case INIT:
200 throw new RuntimeException("parsing error: more closing than opening elements");
201 case ENTRY:
202 if ("entry".equals(qName)) {
203 if (!skipEntry) {
204 entries.add(entry);
205 }
206 entry = null;
207 }
208 break;
209 case ENTRY_ATTRIBUTE:
210 switch(qName) {
211 case "name":
212 entry.setName(lang == null ? LanguageInfo.getJOSMLocaleCode(null) : lang, accumulator.toString());
213 break;
214 case "description":
215 entry.setDescription(lang, accumulator.toString());
216 break;
217 case "id":
218 entry.setId(accumulator.toString());
219 break;
220 case "type":
221 boolean found = false;
222 for (ImageryType type : ImageryType.values()) {
223 if (Objects.equals(accumulator.toString(), type.getTypeString())) {
224 entry.setImageryType(type);
225 found = true;
226 break;
227 }
228 }
229 if (!found) {
230 skipEntry = true;
231 }
232 break;
233 case "default":
234 switch (accumulator.toString()) {
235 case "true":
236 entry.setDefaultEntry(true);
237 break;
238 case "false":
239 entry.setDefaultEntry(false);
240 break;
241 default:
242 skipEntry = true;
243 }
244 break;
245 case "url":
246 entry.setUrl(accumulator.toString());
247 break;
248 case "eula":
249 entry.setEulaAcceptanceRequired(accumulator.toString());
250 break;
251 case "min-zoom":
252 case "max-zoom":
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 {
262 if ("min-zoom".equals(qName)) {
263 entry.setDefaultMinZoom(val);
264 } else {
265 entry.setDefaultMaxZoom(val);
266 }
267 }
268 break;
269 case "attribution-text":
270 entry.setAttributionText(accumulator.toString());
271 break;
272 case "attribution-url":
273 entry.setAttributionLinkURL(accumulator.toString());
274 break;
275 case "logo-image":
276 entry.setAttributionImage(accumulator.toString());
277 break;
278 case "logo-url":
279 entry.setAttributionImageURL(accumulator.toString());
280 break;
281 case "terms-of-use-text":
282 entry.setTermsOfUseText(accumulator.toString());
283 break;
284 case "terms-of-use-url":
285 entry.setTermsOfUseURL(accumulator.toString());
286 break;
287 case "country-code":
288 entry.setCountryCode(accumulator.toString());
289 break;
290 case "icon":
291 entry.setIcon(accumulator.toString());
292 break;
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;
310 }
311 }
312 }
313}
Note: See TracBrowser for help on using the repository browser.