From d6502cdaec658d748ef640bdf086c4be1ccdb131 Mon Sep 17 00:00:00 2001 From: Jody Garnett Date: Tue, 13 Oct 2026 11:33:39 -0700 Subject: [PATCH] GMLReader and KMLReader XML parser configuration GML and KML do not require DOCTYPE. Apply recommended parser settings to both readers. --- .../locationtech/jts/io/gml2/GMLReader.java | 40 ++++++++++++++++++- .../locationtech/jts/io/kml/KMLReader.java | 30 ++++++++++++-- .../jts/io/gml2/GMLReaderTest.java | 31 ++++++++++++++ .../jts/io/kml/KMLReaderTest.java | 5 +++ 4 files changed, 101 insertions(+), 5 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java b/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java index 7bef25880f..e408dd7f2c 100644 --- a/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java +++ b/modules/core/src/main/java/org/locationtech/jts/io/gml2/GMLReader.java @@ -15,6 +15,7 @@ import java.io.Reader; import java.io.StringReader; +import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; @@ -25,6 +26,8 @@ import org.locationtech.jts.geom.PrecisionModel; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; import org.xml.sax.helpers.DefaultHandler; @@ -105,16 +108,51 @@ public Geometry read(Reader reader, GeometryFactory geometryFactory) throws SAXE fact.setNamespaceAware(false); fact.setValidating(false); + // GML does not require DOCTYPE + boolean secureProcessing = setFeature(fact, XMLConstants.FEATURE_SECURE_PROCESSING, true); + boolean disallowDocType = setFeature(fact, "http://apache.org/xml/features/disallow-doctype-decl", true); + if (!secureProcessing && !disallowDocType) { + throw new ParserConfigurationException("Parser does not support DOCTYPE restrictions"); + } + setFeature(fact, "http://xml.org/sax/features/external-general-entities", false); + setFeature(fact, "http://xml.org/sax/features/external-parameter-entities", false); + setFeature(fact, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + try { + fact.setXIncludeAware(false); + } catch (UnsupportedOperationException e) { + // not supported by this parser + } SAXParser parser = fact.newSAXParser(); + try { + parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (SAXNotRecognizedException | SAXNotSupportedException e) { + // not supported by this parser + } if(geometryFactory == null) geometryFactory = new GeometryFactory(); - GMLHandler gh = new GMLHandler(geometryFactory,null); + GMLHandler gh = new GMLHandler(geometryFactory,null) { + @Override + public InputSource resolveEntity(String publicId, String systemId) throws SAXException { + throw new SAXException("External entity resolution is not allowed: " + systemId); + } + }; parser.parse(new InputSource(reader), (DefaultHandler)gh); return gh.getGeometry(); } + private static boolean setFeature(SAXParserFactory fact, String feature, boolean value) { + try { + fact.setFeature(feature, value); + return true; + } catch (ParserConfigurationException | SAXNotRecognizedException | SAXNotSupportedException e) { + // not supported by this parser + return false; + } + } + } diff --git a/modules/core/src/main/java/org/locationtech/jts/io/kml/KMLReader.java b/modules/core/src/main/java/org/locationtech/jts/io/kml/KMLReader.java index 1b9fb562c7..62a35548e4 100644 --- a/modules/core/src/main/java/org/locationtech/jts/io/kml/KMLReader.java +++ b/modules/core/src/main/java/org/locationtech/jts/io/kml/KMLReader.java @@ -21,6 +21,7 @@ import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.io.ParseException; +import javax.xml.XMLConstants; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; @@ -57,6 +58,8 @@ public class KMLReader { private static final String OUTER_BOUNDARY_IS = "outerBoundaryIs"; private static final String INNER_BOUNDARY_IS = "innerBoundaryIs"; + private static final String ENTITY_EXPANSION_LIMIT = "jdk.xml.entityExpansionLimit"; + private static final String NO_ELEMENT_ERROR = "No element %s found in %s"; /** @@ -97,10 +100,29 @@ public KMLReader(GeometryFactory geometryFactory, Collection attributeNa this.attributeNames = attributeNames == null ? Collections.emptySet() : new HashSet<>(attributeNames); - // Disable DTDs completely (prevents DOCTYPE declarations) - inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); - // Prevent external entity expansion from DTDs - inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); + // KML does not require DOCTYPE + boolean disallowDtd = setProperty(XMLInputFactory.SUPPORT_DTD, false); + if (!disallowDtd) { + throw new IllegalStateException("Parser does not support DOCTYPE restrictions"); + } + setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); + setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + if (inputFactory.isPropertySupported(ENTITY_EXPANSION_LIMIT) + && "0".equals(String.valueOf(inputFactory.getProperty(ENTITY_EXPANSION_LIMIT)))) { + inputFactory.setProperty(ENTITY_EXPANSION_LIMIT, "64000"); + } + inputFactory.setXMLResolver((publicID, systemID, baseURI, namespace) -> { + throw new XMLStreamException("External entity resolution is not allowed: " + systemID); + }); + } + + private boolean setProperty(String name, Object value) { + if (inputFactory.isPropertySupported(name)) { + inputFactory.setProperty(name, value); + return true; + } + return false; } /** diff --git a/modules/core/src/test/java/org/locationtech/jts/io/gml2/GMLReaderTest.java b/modules/core/src/test/java/org/locationtech/jts/io/gml2/GMLReaderTest.java index debbb660da..1dbf5eeee1 100644 --- a/modules/core/src/test/java/org/locationtech/jts/io/gml2/GMLReaderTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/io/gml2/GMLReaderTest.java @@ -1,6 +1,8 @@ package org.locationtech.jts.io.gml2; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import javax.xml.parsers.ParserConfigurationException; @@ -126,4 +128,33 @@ private void checkRead(String gml, String wktExpected, int srid) { checkEqual(expected, g); assertEquals("SRID incorrect - ", srid, g.getSRID()); } + + public void testExternalCoordinatesNotIncluded() throws Exception { + File coordinates = File.createTempFile("coordinates", ".txt"); + try { + Files.write(coordinates.toPath(), "5,10".getBytes("UTF-8")); + String gml = " ]>" + + "&coordinates;"; + try { + new GMLReader().read(gml, null); + fail("external coordinates should not be included"); + } + catch (SAXException expected) { + } + } + finally { + coordinates.delete(); + } + } + + public void testDoctypeIsRejected() throws Exception { + String gml = "\n\n" + + "5,10"; + try { + new GMLReader().read(gml, null); + fail("expected a DOCTYPE to be rejected"); + } + catch (Exception e) { + } + } } diff --git a/modules/core/src/test/java/org/locationtech/jts/io/kml/KMLReaderTest.java b/modules/core/src/test/java/org/locationtech/jts/io/kml/KMLReaderTest.java index 742ca4351b..65e93b8b89 100644 --- a/modules/core/src/test/java/org/locationtech/jts/io/kml/KMLReaderTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/io/kml/KMLReaderTest.java @@ -177,6 +177,11 @@ public void testCoordinatesErrors() { checkExceptionThrown("", "No element coordinates found in innerBoundaryIs"); } + public void testDoctypeIgnored() { + checkParsingResult("1.0,1.0", + "POINT (1 1)", new Map[]{null}); + } + public void testUnknownGeometryType() { checkExceptionThrown("", "Unknown KML geometry type StrangePoint"); }