From 1fff0ab566235107146fefabab007916e28cda98 Mon Sep 17 00:00:00 2001 From: Artem Smirnov Date: Tue, 2 Jun 2026 22:13:05 +0300 Subject: [PATCH] Tolerate unsupported XML parser security options --- .../main/java/opennlp/tools/util/XmlUtil.java | 87 +++++++++++++++--- .../java/opennlp/tools/util/XmlUtilTest.java | 92 +++++++++++++++++++ 2 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 opennlp-tools/src/test/java/opennlp/tools/util/XmlUtilTest.java diff --git a/opennlp-tools/src/main/java/opennlp/tools/util/XmlUtil.java b/opennlp-tools/src/main/java/opennlp/tools/util/XmlUtil.java index 96d2cfb7c..956f836e5 100644 --- a/opennlp-tools/src/main/java/opennlp/tools/util/XmlUtil.java +++ b/opennlp-tools/src/main/java/opennlp/tools/util/XmlUtil.java @@ -27,6 +27,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; public class XmlUtil { @@ -49,17 +51,17 @@ public static DocumentBuilder createDocumentBuilder() { " this platform.", e); } try { - documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); - documentBuilderFactory.setFeature( + setAttributeIfSupported(documentBuilderFactory, XMLConstants.ACCESS_EXTERNAL_DTD, ""); + setAttributeIfSupported(documentBuilderFactory, XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + setFeatureIfSupported(documentBuilderFactory, "http://apache.org/xml/features/disallow-doctype-decl", true); - documentBuilderFactory.setFeature( + setFeatureIfSupported(documentBuilderFactory, "http://xml.org/sax/features/external-general-entities", false); - documentBuilderFactory.setFeature( + setFeatureIfSupported(documentBuilderFactory, "http://xml.org/sax/features/external-parameter-entities", false); - documentBuilderFactory.setFeature( + setFeatureIfSupported(documentBuilderFactory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - documentBuilderFactory.setXIncludeAware(false); + setXIncludeAwareIfSupported(documentBuilderFactory, false); documentBuilderFactory.setExpandEntityReferences(false); return documentBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { @@ -76,7 +78,7 @@ public static DocumentBuilder createDocumentBuilder() { public static SAXParser createSaxParser() { final SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setNamespaceAware(true); - spf.setXIncludeAware(false); + setXIncludeAwareIfSupported(spf, false); try { spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); } catch (ParserConfigurationException | SAXException e) { @@ -85,17 +87,74 @@ public static SAXParser createSaxParser() { logger.warn("Failed to enable XMLConstants.FEATURE_SECURE_PROCESSING, it's unsupported on" + " this platform.", e); } + setFeatureIfSupported(spf, "http://apache.org/xml/features/disallow-doctype-decl", true); + setFeatureIfSupported(spf, "http://xml.org/sax/features/external-general-entities", false); + setFeatureIfSupported(spf, "http://xml.org/sax/features/external-parameter-entities", false); + setFeatureIfSupported(spf, "http://apache.org/xml/features/nonvalidating/load-external-dtd", + false); try { - spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - spf.setFeature("http://xml.org/sax/features/external-general-entities", false); - spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); final SAXParser parser = spf.newSAXParser(); - parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + setPropertyIfSupported(parser, XMLConstants.ACCESS_EXTERNAL_DTD, ""); + setPropertyIfSupported(parser, XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); return parser; } catch (ParserConfigurationException | SAXException e) { throw new IllegalStateException(e); } } + + private static void setFeatureIfSupported(DocumentBuilderFactory factory, String name, + boolean value) { + try { + factory.setFeature(name, value); + } catch (ParserConfigurationException e) { + logger.warn("Failed to set XML parser feature {}, it's unsupported on this platform.", + name, e); + } + } + + private static void setAttributeIfSupported(DocumentBuilderFactory factory, String name, + Object value) { + try { + factory.setAttribute(name, value); + } catch (IllegalArgumentException e) { + logger.warn("Failed to set XML parser attribute {}, it's unsupported on this platform.", + name, e); + } + } + + private static void setXIncludeAwareIfSupported(DocumentBuilderFactory factory, boolean state) { + try { + factory.setXIncludeAware(state); + } catch (UnsupportedOperationException e) { + logger.warn("Failed to set XML parser XInclude awareness, it's unsupported on " + + "this platform.", e); + } + } + + private static void setPropertyIfSupported(SAXParser parser, String name, Object value) { + try { + parser.setProperty(name, value); + } catch (SAXNotRecognizedException | SAXNotSupportedException e) { + logger.warn("Failed to set XML parser property {}, it's unsupported on this platform.", + name, e); + } + } + + private static void setFeatureIfSupported(SAXParserFactory factory, String name, boolean value) { + try { + factory.setFeature(name, value); + } catch (ParserConfigurationException | SAXException e) { + logger.warn("Failed to set XML parser feature {}, it's unsupported on this platform.", + name, e); + } + } + + private static void setXIncludeAwareIfSupported(SAXParserFactory factory, boolean state) { + try { + factory.setXIncludeAware(state); + } catch (UnsupportedOperationException e) { + logger.warn("Failed to set XML parser XInclude awareness, it's unsupported on " + + "this platform.", e); + } + } } diff --git a/opennlp-tools/src/test/java/opennlp/tools/util/XmlUtilTest.java b/opennlp-tools/src/test/java/opennlp/tools/util/XmlUtilTest.java new file mode 100644 index 000000000..e5cd82ae8 --- /dev/null +++ b/opennlp-tools/src/test/java/opennlp/tools/util/XmlUtilTest.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package opennlp.tools.util; + +import java.io.StringReader; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.xml.sax.InputSource; + +public class XmlUtilTest { + + @Test + void testCreateDocumentBuilderWithUnsupportedSecurityOptions() throws Exception { + String property = DocumentBuilderFactory.class.getName(); + String oldFactory = System.getProperty(property); + System.setProperty(property, ThrowingSecurityOptionsDocumentBuilderFactory.class.getName()); + try { + DocumentBuilder documentBuilder = XmlUtil.createDocumentBuilder(); + + Assertions.assertEquals("root", documentBuilder.parse( + new InputSource(new StringReader(""))).getDocumentElement().getTagName()); + } finally { + if (oldFactory == null) { + System.clearProperty(property); + } else { + System.setProperty(property, oldFactory); + } + } + } + + public static class ThrowingSecurityOptionsDocumentBuilderFactory + extends DocumentBuilderFactory { + + private final DocumentBuilderFactory delegate = DocumentBuilderFactory.newDefaultInstance(); + + @Override + public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { + return delegate.newDocumentBuilder(); + } + + @Override + public void setAttribute(String name, Object value) { + if (XMLConstants.ACCESS_EXTERNAL_DTD.equals(name)) { + throw new IllegalArgumentException(name); + } + delegate.setAttribute(name, value); + } + + @Override + public Object getAttribute(String name) { + return delegate.getAttribute(name); + } + + @Override + public void setFeature(String name, boolean value) throws ParserConfigurationException { + if ("http://apache.org/xml/features/disallow-doctype-decl".equals(name)) { + throw new ParserConfigurationException(name); + } + delegate.setFeature(name, value); + } + + @Override + public void setXIncludeAware(boolean state) { + throw new UnsupportedOperationException("XInclude"); + } + + @Override + public boolean getFeature(String name) throws ParserConfigurationException { + return delegate.getFeature(name); + } + } +}