Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions bag2-loader/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ SPDX-License-Identifier: MIT
<artifactId>staxmate</artifactId>
</dependency>
<dependency>
<!-- Aalto is a fast StAX parser, used via XMLInputFactory SPI -->
<groupId>com.fasterxml</groupId>
<artifactId>aalto-xml</artifactId>
<groupId>org.apache.commons</groupId>
<artifactId>commons-secure-xml</artifactId>
</dependency>
<dependency>
<!-- StaxMate requires a Stax2 implementation such as Woodstox, with the JDK parser
elements after a geometry read by XmlStreamGeometryReader are skipped -->
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import nl.b3p.brmo.bag2.schema.BAG2Object;
import nl.b3p.brmo.bag2.schema.BAG2ObjectType;
import nl.b3p.brmo.bag2.schema.BAG2Schema;
import nl.b3p.brmo.bag2.util.Force2DCoordinateSequenceFactory;
import nl.b3p.brmo.util.StaxMateFactories;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.staxmate.SMInputFactory;
Expand Down Expand Up @@ -163,21 +163,7 @@ public BAG2GMLMutatieGroepStream(InputStream in) throws XMLStreamException {
}

protected SMInputFactory buildSMInputFactory() {
// Using alternative StAX parsers explicitly:
// final XMLInputFactory stax = new WstxInputFactory(); // Woodstox
// final XMLInputFactory stax = new com.fasterxml.aalto.stax.InputFactoryImpl(); // Aalto
final XMLInputFactory xmlInputFactory =
XMLInputFactory.newFactory(); // JRE Default, depends on JAR's present or
// javax.xml.stream.XMLInputFactory property, can be SJSXP
log.trace("StAX XMLInputFactory: " + xmlInputFactory.getClass().getName());

xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); // Coalesce characters
xmlInputFactory.setProperty(
XMLInputFactory.SUPPORT_DTD,
Boolean.FALSE); // No XML entity expansions or external entities
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);

return new SMInputFactory(xmlInputFactory);
return StaxMateFactories.newSMInputFactory();
}

protected XmlStreamGeometryReader buildGeometryReader() {
Expand Down
10 changes: 8 additions & 2 deletions bgt-loader/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@ SPDX-License-Identifier: MIT
<artifactId>staxmate</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml</groupId>
<artifactId>aalto-xml</artifactId>
<groupId>org.apache.commons</groupId>
<artifactId>commons-secure-xml</artifactId>
</dependency>
<dependency>
<!-- StaxMate requires a Stax2 implementation such as Woodstox, with the JDK parser
elements after a geometry read by XmlStreamGeometryReader are skipped -->
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
import java.util.Queue;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import nl.b3p.brmo.bgt.schema.BGTObject;
import nl.b3p.brmo.bgt.schema.BGTObjectType;
import nl.b3p.brmo.bgt.schema.BGTSchema;
import nl.b3p.brmo.util.StaxMateFactories;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.staxmate.SMInputFactory;
Expand Down Expand Up @@ -122,10 +122,6 @@ private SMInputCursor initCursor(SMInputCursor cursor) throws XMLStreamException
throw new IllegalArgumentException("XML root element moet CityModel of bgtMutaties zijn");
}

// Note: when using StaxMate childElementCursor() or similar, only an local name string
// element does not work
// when using Aalto. Always use a QName parameter!

if (isMutaties) {
cursor = cursor.childElementCursor(MUTATIE_BERICHT).advance().childElementCursor().advance();
label:
Expand Down Expand Up @@ -184,21 +180,7 @@ protected XmlStreamGeometryReader buildGeometryReader() {
}

protected SMInputFactory buildSMInputFactory() {
// Using alternative StAX parsers explicitly:
// final XMLInputFactory stax = new WstxInputFactory(); // Woodstox
// final XMLInputFactory stax = new com.fasterxml.aalto.stax.InputFactoryImpl(); // Aalto
final XMLInputFactory xmlInputFactory =
XMLInputFactory.newFactory(); // JRE Default, depends on JAR's present or
// javax.xml.stream.XMLInputFactory property, can be SJSXP
log.trace("StAX XMLInputFactory: " + xmlInputFactory.getClass().getName());

xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); // Coalesce characters
xmlInputFactory.setProperty(
XMLInputFactory.SUPPORT_DTD,
Boolean.FALSE); // No XML entity expansions or external entities
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);

return new SMInputFactory(xmlInputFactory);
return StaxMateFactories.newSMInputFactory();
}

@Override
Expand Down
69 changes: 69 additions & 0 deletions bgt-loader/src/main/java/nl/b3p/brmo/util/StaxMateFactories.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2026 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/

package nl.b3p.brmo.util;

import java.io.StringReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.xml.secure.SecureXMLInputFactory;
import org.codehaus.stax2.XMLStreamReader2;
import org.codehaus.staxmate.SMInputFactory;

/** Factory for StaxMate input factories for parsing GML with StaxMate and GeoTools. */
public final class StaxMateFactories {
private StaxMateFactories() {}

/**
* Create a StaxMate input factory using a StAX implementation which is secured against XXE by
* Apache Commons Secure XML, coalesces characters and does not support DTDs.
*
* <p>The StAX implementation must natively implement Stax2, such as Woodstox. For other
* implementations (such as the JDK default) StaxMate wraps the reader in a {@link
* org.codehaus.stax2.ri.Stax2ReaderAdapter} which does not track the element depth for {@link
* XMLStreamReader#nextTag()}. The GeoTools XmlStreamGeometryReader uses that method, so after
* reading some geometries (such as curves) StaxMate cursors silently skip the elements after it.
*
* <p>The GeoTools geometry reader reads from {@link
* org.codehaus.staxmate.in.SMInputCursor#getStreamReader()} directly, bypassing the cursors.
* StaxMate allows this but discourages it, because advancing the reader directly can desync the
* cursors (see <a href="https://github.com/FasterXML/StaxMate/issues/2">StaxMate issue #2</a>).
* As of StaxMate 2.4.2 this works because cursors resync using {@code getDepth()} of the reader,
* which is only correct for all methods with a native Stax2 implementation, and because the
* geometry is read within the element of a child cursor which is not used afterward.
*
* <p>If a future StaxMate version resolves issue #2 (for instance by wrapping the returned reader
* to keep the cursors synchronized), this check may no longer be needed. Note that StaxMate may
* be upgraded by automatic dependency updates without anyone checking whether that issue has been
* resolved, so keep the check unless it has been verified to be unnecessary.
*
* @return the StaxMate input factory
* @throws IllegalStateException if the StAX implementation does not natively implement Stax2
*/
public static SMInputFactory newSMInputFactory() {
final XMLInputFactory xmlInputFactory = SecureXMLInputFactory.newFactory();
xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); // Coalesce characters
xmlInputFactory.setProperty(
XMLInputFactory.SUPPORT_DTD,
Boolean.FALSE); // No XML entity expansions or external entities

try {
XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(new StringReader("<r/>"));
if (!(reader instanceof XMLStreamReader2)) {
throw new IllegalStateException(
"StAX implementation "
+ reader.getClass().getName()
+ " does not implement Stax2, use Woodstox");
}
reader.close();
} catch (XMLStreamException e) {
throw new IllegalStateException("Error checking StAX implementation", e);

Check warning on line 64 in bgt-loader/src/main/java/nl/b3p/brmo/util/StaxMateFactories.java

View check run for this annotation

Codecov / codecov/patch

bgt-loader/src/main/java/nl/b3p/brmo/util/StaxMateFactories.java#L63-L64

Added lines #L63 - L64 were not covered by tests
}

return new SMInputFactory(xmlInputFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2026 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*
*/

package nl.b3p.brmo.util;

import static org.junit.jupiter.api.Assertions.*;

import java.io.StringReader;
import org.codehaus.stax2.ri.Stax2ReaderAdapter;
import org.junit.jupiter.api.Test;

class StaxMateFactoriesTest {
private static final String FACTORY_PROPERTY = "javax.xml.stream.XMLInputFactory";

@Test
void usesNativeStax2Reader() throws Exception {
assertFalse(
StaxMateFactories.newSMInputFactory()
.rootElementCursor(new StringReader("<r/>"))
.getStreamReader()
instanceof Stax2ReaderAdapter);
}

@Test
void rejectsJdkStaxImplementation() {
String previous = System.getProperty(FACTORY_PROPERTY);
System.setProperty(FACTORY_PROPERTY, "com.sun.xml.internal.stream.XMLInputFactoryImpl");
try {
IllegalStateException e =
assertThrows(IllegalStateException.class, StaxMateFactories::newSMInputFactory);
assertTrue(e.getMessage().contains("does not implement Stax2"), e.getMessage());
} finally {
if (previous == null) {
System.clearProperty(FACTORY_PROPERTY);
} else {
System.setProperty(FACTORY_PROPERTY, previous);
}
}
}
}
12 changes: 0 additions & 12 deletions brmo-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@
<groupId>net.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml</groupId>
<artifactId>aalto-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand All @@ -119,10 +115,6 @@
<groupId>net.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml</groupId>
<artifactId>aalto-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand All @@ -141,10 +133,6 @@
<groupId>net.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml</groupId>
<artifactId>aalto-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand Down
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -575,9 +575,9 @@
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml</groupId>
<artifactId>aalto-xml</artifactId>
<version>1.4.0</version>
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>
<version>7.3.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.staxmate</groupId>
Expand Down
Loading