This is an automated email from the ASF dual-hosted git repository. ppkarwasz pushed a commit to branch feature/jaxp-factory-methods in repository https://gitbox.apache.org/repos/asf/commons-xml.git
commit 444dc82fc90e2e670722b91f68c2ceeb89c4c944 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Thu Aug 27 22:40:12 2026 +0200 Add the Java 13 newNSInstance factory methods, resolved at runtime DocumentBuilderFactory and SAXParserFactory gain the newNSInstance, newNSInstance(String, ClassLoader) and newDefaultNSInstance mirrors, resolved through MethodHandles.publicLookup() like the Java 9 methods. Where the platform predates them, the fallback enables namespace awareness on the corresponding newInstance lookup, the behavior the JAXP methods are specified to have, so the non-default variants work on every supported platform including Android; newDefaultNSInstance inherits the newDefaultInstance fallback chain. Assisted-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WWJ4LAxx3TX5RwNvwKbAS3 --- .../xml/HardeningDocumentBuilderFactory.java | 98 ++++++++++++++++++++++ .../commons/xml/HardeningSAXParserFactory.java | 98 ++++++++++++++++++++++ .../commons/xml/HardeningFactoriesSmokeTest.java | 64 ++++++++++++++ 3 files changed, 260 insertions(+) diff --git a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java index 5909133..aee284c 100644 --- a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java +++ b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilderFactory.java @@ -56,6 +56,13 @@ public final class HardeningDocumentBuilderFactory { private static final MethodHandle NEW_DEFAULT_INSTANCE = findStatic("newDefaultInstance", MethodType.methodType(DocumentBuilderFactory.class)); + private static final MethodHandle NEW_DEFAULT_NS_INSTANCE = findStatic("newDefaultNSInstance", MethodType.methodType(DocumentBuilderFactory.class)); + + private static final MethodHandle NEW_NS_INSTANCE = findStatic("newNSInstance", MethodType.methodType(DocumentBuilderFactory.class)); + + private static final MethodHandle NEW_NS_INSTANCE_BY_CLASS_NAME = findStatic("newNSInstance", + MethodType.methodType(DocumentBuilderFactory.class, String.class, ClassLoader.class)); + private static MethodHandle findStatic(final String name, final MethodType type) { try { return MethodHandles.publicLookup().findStatic(DocumentBuilderFactory.class, name, type); @@ -127,6 +134,36 @@ public static DocumentBuilderFactory newDefaultInstance() { return newInstance(JDK_DOCUMENT_BUILDER_FACTORY, null); } + /** + * Returns a new, hardened, namespace-aware {@link DocumentBuilderFactory} of the system-default implementation. + * <p> + * Obtained as by {@code DocumentBuilderFactory.newDefaultNSInstance()} where the platform provides it (Java 13 or later), and by enabling namespace + * awareness on {@link #newDefaultInstance()} otherwise, the behavior the JAXP method is specified to have. + * </p> + * + * @return A hardened, namespace-aware factory. + * @throws IllegalStateException Thrown if a required hardening setting cannot be applied to the underlying implementation. + * @throws FactoryConfigurationError Thrown if the running platform provides neither {@code newDefaultInstance()} nor the JDK's built-in implementation + * (for example Android). + */ + public static DocumentBuilderFactory newDefaultNSInstance() { + if (NEW_DEFAULT_NS_INSTANCE != null) { + final DocumentBuilderFactory factory; + try { + factory = (DocumentBuilderFactory) NEW_DEFAULT_NS_INSTANCE.invokeExact(); + } catch (final FactoryConfigurationError e) { + throw e; + } catch (final Throwable e) { + // Unreachable: the looked-up method declares no other exceptions. + throw new IllegalStateException(e); + } + return harden(factory); + } + final DocumentBuilderFactory factory = newDefaultInstance(); + factory.setNamespaceAware(true); + return factory; + } + /** * Returns a new, hardened {@link DocumentBuilderFactory}. * @@ -156,6 +193,67 @@ public static DocumentBuilderFactory newInstance(final String factoryClassName, return harden(DocumentBuilderFactory.newInstance(factoryClassName, classLoader)); } + /** + * Returns a new, hardened, namespace-aware {@link DocumentBuilderFactory}. + * <p> + * Obtained as by {@code DocumentBuilderFactory.newNSInstance()} where the platform provides it (Java 13 or later), and by enabling namespace awareness on + * {@link #newInstance()} otherwise, the behavior the JAXP method is specified to have. + * </p> + * + * @return A hardened, namespace-aware factory. + * @throws IllegalStateException Thrown if a required hardening setting cannot be applied to the underlying implementation. + * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the + * implementation is not available or cannot be instantiated. + */ + public static DocumentBuilderFactory newNSInstance() { + if (NEW_NS_INSTANCE != null) { + final DocumentBuilderFactory factory; + try { + factory = (DocumentBuilderFactory) NEW_NS_INSTANCE.invokeExact(); + } catch (final FactoryConfigurationError e) { + throw e; + } catch (final Throwable e) { + // Unreachable: the looked-up method declares no other exceptions. + throw new IllegalStateException(e); + } + return harden(factory); + } + final DocumentBuilderFactory factory = newInstance(); + factory.setNamespaceAware(true); + return factory; + } + + /** + * Returns a new, hardened, namespace-aware {@link DocumentBuilderFactory} of the given implementation class. + * <p> + * Obtained as by {@code DocumentBuilderFactory.newNSInstance(String, ClassLoader)} where the platform provides it (Java 13 or later), and by enabling + * namespace awareness on {@link #newInstance(String, ClassLoader)} otherwise, the behavior the JAXP method is specified to have. + * </p> + * + * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation. + * @param classLoader The class loader used to load the factory class; {@code null} means the current thread's context class loader. + * @return A hardened, namespace-aware factory. + * @throws IllegalStateException Thrown if a required hardening setting cannot be applied to the underlying implementation. + * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated. + */ + public static DocumentBuilderFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) { + if (NEW_NS_INSTANCE_BY_CLASS_NAME != null) { + final DocumentBuilderFactory factory; + try { + factory = (DocumentBuilderFactory) NEW_NS_INSTANCE_BY_CLASS_NAME.invokeExact(factoryClassName, classLoader); + } catch (final FactoryConfigurationError e) { + throw e; + } catch (final Throwable e) { + // Unreachable: the looked-up method declares no other exceptions. + throw new IllegalStateException(e); + } + return harden(factory); + } + final DocumentBuilderFactory factory = newInstance(factoryClassName, classLoader); + factory.setNamespaceAware(true); + return factory; + } + /** * Sets a feature on the given factory, throwing a {@link HardeningException} if the implementation does not recognize it. * diff --git a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java index eb16def..36d4585 100644 --- a/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java +++ b/src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java @@ -66,6 +66,13 @@ public final class HardeningSAXParserFactory { private static final MethodHandle NEW_DEFAULT_INSTANCE = findStatic("newDefaultInstance", MethodType.methodType(SAXParserFactory.class)); + private static final MethodHandle NEW_DEFAULT_NS_INSTANCE = findStatic("newDefaultNSInstance", MethodType.methodType(SAXParserFactory.class)); + + private static final MethodHandle NEW_NS_INSTANCE = findStatic("newNSInstance", MethodType.methodType(SAXParserFactory.class)); + + private static final MethodHandle NEW_NS_INSTANCE_BY_CLASS_NAME = findStatic("newNSInstance", + MethodType.methodType(SAXParserFactory.class, String.class, ClassLoader.class)); + private static MethodHandle findStatic(final String name, final MethodType type) { try { return MethodHandles.publicLookup().findStatic(SAXParserFactory.class, name, type); @@ -184,6 +191,36 @@ public static SAXParserFactory newDefaultInstance() { return newInstance(JDK_SAX_PARSER_FACTORY, null); } + /** + * Returns a new, hardened, namespace-aware {@link SAXParserFactory} of the system-default implementation. + * <p> + * Obtained as by {@code SAXParserFactory.newDefaultNSInstance()} where the platform provides it (Java 13 or later), and by enabling namespace awareness on + * {@link #newDefaultInstance()} otherwise, the behavior the JAXP method is specified to have. + * </p> + * + * @return A hardened, namespace-aware factory. + * @throws IllegalStateException Thrown if a required hardening setting cannot be applied to the underlying implementation. + * @throws FactoryConfigurationError Thrown if the running platform provides neither {@code newDefaultInstance()} nor the JDK's built-in implementation + * (for example Android). + */ + public static SAXParserFactory newDefaultNSInstance() { + if (NEW_DEFAULT_NS_INSTANCE != null) { + final SAXParserFactory factory; + try { + factory = (SAXParserFactory) NEW_DEFAULT_NS_INSTANCE.invokeExact(); + } catch (final FactoryConfigurationError e) { + throw e; + } catch (final Throwable e) { + // Unreachable: the looked-up method declares no other exceptions. + throw new IllegalStateException(e); + } + return harden(factory); + } + final SAXParserFactory factory = newDefaultInstance(); + factory.setNamespaceAware(true); + return factory; + } + /** * Creates a new hardened, namespace-aware {@link XMLReader} for the TrAX wrappers to parse sources with. * @@ -227,6 +264,67 @@ public static SAXParserFactory newInstance(final String factoryClassName, final return harden(SAXParserFactory.newInstance(factoryClassName, classLoader)); } + /** + * Returns a new, hardened, namespace-aware {@link SAXParserFactory}. + * <p> + * Obtained as by {@code SAXParserFactory.newNSInstance()} where the platform provides it (Java 13 or later), and by enabling namespace awareness on + * {@link #newInstance()} otherwise, the behavior the JAXP method is specified to have. + * </p> + * + * @return A hardened, namespace-aware factory. + * @throws IllegalStateException Thrown if a required hardening setting cannot be applied to the underlying implementation. + * @throws FactoryConfigurationError Thrown from {@link SAXParserFactory} in case of a {@link java.util.ServiceConfigurationError service configuration + * error} or if the implementation is not available or cannot be instantiated. + */ + public static SAXParserFactory newNSInstance() { + if (NEW_NS_INSTANCE != null) { + final SAXParserFactory factory; + try { + factory = (SAXParserFactory) NEW_NS_INSTANCE.invokeExact(); + } catch (final FactoryConfigurationError e) { + throw e; + } catch (final Throwable e) { + // Unreachable: the looked-up method declares no other exceptions. + throw new IllegalStateException(e); + } + return harden(factory); + } + final SAXParserFactory factory = newInstance(); + factory.setNamespaceAware(true); + return factory; + } + + /** + * Returns a new, hardened, namespace-aware {@link SAXParserFactory} of the given implementation class. + * <p> + * Obtained as by {@code SAXParserFactory.newNSInstance(String, ClassLoader)} where the platform provides it (Java 13 or later), and by enabling namespace + * awareness on {@link #newInstance(String, ClassLoader)} otherwise, the behavior the JAXP method is specified to have. + * </p> + * + * @param factoryClassName The fully qualified class name of the {@link SAXParserFactory} implementation. + * @param classLoader The class loader used to load the factory class; {@code null} means the current thread's context class loader. + * @return A hardened, namespace-aware factory. + * @throws IllegalStateException Thrown if a required hardening setting cannot be applied to the underlying implementation. + * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated. + */ + public static SAXParserFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) { + if (NEW_NS_INSTANCE_BY_CLASS_NAME != null) { + final SAXParserFactory factory; + try { + factory = (SAXParserFactory) NEW_NS_INSTANCE_BY_CLASS_NAME.invokeExact(factoryClassName, classLoader); + } catch (final FactoryConfigurationError e) { + throw e; + } catch (final Throwable e) { + // Unreachable: the looked-up method declares no other exceptions. + throw new IllegalStateException(e); + } + return harden(factory); + } + final SAXParserFactory factory = newInstance(factoryClassName, classLoader); + factory.setNamespaceAware(true); + return factory; + } + private static void setFeature(final SAXParserFactory factory, final String feature, final boolean value) { try { factory.setFeature(feature, value); diff --git a/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java b/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java index 69acdd8..0b4372f 100644 --- a/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java +++ b/src/test/java/org/apache/commons/xml/HardeningFactoriesSmokeTest.java @@ -226,6 +226,70 @@ void newDefaultInstanceSAXParserFactoryIsUsable() throws Exception { assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)); } + // The newNSInstance family (Java 13) falls back to enabling namespace awareness on the corresponding newInstance lookup, the behavior the JAXP methods + // are specified to have, so the non-default variants work on every platform including Android. + @Test + @Tag("dom") + void newNSInstanceDocumentBuilderFactoryIsNamespaceAware() throws Exception { + final DocumentBuilderFactory factory = HardeningDocumentBuilderFactory.newNSInstance(); + assertTrue(factory.isNamespaceAware()); + assertNotNull(factory.newDocumentBuilder().parse(new InputSource(new StringReader(BENIGN_XML))).getDocumentElement()); + if (!AttackTestSupport.IS_ANDROID) { + assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)); + } + } + + @Test + @Tag("dom") + void newDefaultNSInstanceDocumentBuilderFactoryIsNamespaceAware() throws Exception { + if (AttackTestSupport.IS_ANDROID) { + assertThrows(FactoryConfigurationError.class, HardeningDocumentBuilderFactory::newDefaultNSInstance); + return; + } + final DocumentBuilderFactory factory = HardeningDocumentBuilderFactory.newDefaultNSInstance(); + assertTrue(factory.isNamespaceAware()); + assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)); + } + + @Test + @Tag("sax") + void newNSInstanceSAXParserFactoryIsNamespaceAware() throws Exception { + final SAXParserFactory factory = HardeningSAXParserFactory.newNSInstance(); + assertTrue(factory.isNamespaceAware()); + factory.newSAXParser().parse(new InputSource(new StringReader(BENIGN_XML)), new DefaultHandler()); + if (!AttackTestSupport.IS_ANDROID) { + assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)); + } + } + + @Test + @Tag("sax") + void newDefaultNSInstanceSAXParserFactoryIsNamespaceAware() throws Exception { + if (AttackTestSupport.IS_ANDROID) { + assertThrows(FactoryConfigurationError.class, HardeningSAXParserFactory::newDefaultNSInstance); + return; + } + final SAXParserFactory factory = HardeningSAXParserFactory.newDefaultNSInstance(); + assertTrue(factory.isNamespaceAware()); + assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)); + } + + @Test + void explicitClassNameNSDocumentBuilderFactoryIsNamespaceAware() throws Exception { + final Class<?> impl = DocumentBuilderFactory.newInstance().getClass(); + final DocumentBuilderFactory factory = HardeningDocumentBuilderFactory.newNSInstance(impl.getName(), impl.getClassLoader()); + assertTrue(factory.isNamespaceAware()); + assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)); + } + + @Test + void explicitClassNameNSSAXParserFactoryIsNamespaceAware() throws Exception { + final Class<?> impl = SAXParserFactory.newInstance().getClass(); + final SAXParserFactory factory = HardeningSAXParserFactory.newNSInstance(impl.getName(), impl.getClassLoader()); + assertTrue(factory.isNamespaceAware()); + assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)); + } + @Test void newDefaultInstanceSchemaFactoryIsHardened() throws Exception { final SchemaFactory factory = HardeningSchemaFactory.newDefaultInstance();
