| /* |
| * 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 |
| * |
| * https://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 org.apache.commons.xml.secure; |
| |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertNotEquals; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| import static org.junit.jupiter.api.Assumptions.assumeFalse; |
| import static org.junit.jupiter.api.Assumptions.assumeTrue; |
| |
| import java.io.StringWriter; |
| |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.stream.StreamResult; |
| import javax.xml.validation.SchemaFactory; |
| import javax.xml.xpath.XPathFactory; |
| |
| import org.junit.jupiter.api.Tag; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.api.condition.DisabledInNativeImage; |
| import org.xml.sax.XMLReader; |
| |
| /** |
| * Tests that {@code jdk.xml.overrideDefaultParser} selects which secure parser family performs the source rewrites on factories that recognize the feature. |
| * |
| * <p>The wrapped implementations' internal parsers are never used — the wrappers parse every source themselves — so instead of configuring the delegate the |
| * wrappers read the feature: {@code false} (the JDK's default) pins the platform's built-in parser, {@code true} (or a delegate that does not recognize the |
| * feature) keeps the pluggable lookup. Both choices are secure, so the feature carries no security weight. The tests pin the JDK implementations through |
| * {@code newDefaultInstance()}, so they discriminate in every JVM execution; under test-jdk-xerces the two parser families genuinely differ.</p> |
| */ |
| @Tag("trax") |
| @Tag("xpath") |
| @Tag("schema") |
| class OverrideDefaultParserTest { |
| |
| private static final String FEATURE = SecureSAXParserFactory.OVERRIDE_DEFAULT_PARSER; |
| |
| /** Package prefix of the JDK's built-in parsers, the family a {@code false} feature value pins. */ |
| private static final String JDK_INTERNAL_PREFIX = "com.sun.org.apache.xerces.internal."; |
| |
| /** {@code true} where the runtime's factories know {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER}; JDK 8 gained it in 8u162. */ |
| private static final boolean SUPPORTS_FEATURE = probeFeature(); |
| |
| /** Skips a test on a runtime whose factories do not recognize the feature, where there is no selection to observe. */ |
| private static void assumeFeatureSupported() { |
| assumeTrue(SUPPORTS_FEATURE, "runtime does not recognize " + FEATURE); |
| } |
| |
| private static boolean probeFeature() { |
| try { |
| TransformerFactory.newInstance().setFeature(FEATURE, true); |
| return true; |
| } catch (final Exception e) { |
| return false; |
| } |
| } |
| |
| private static String transform(final TransformerFactory factory, final String text) throws Exception { |
| final Transformer transformer = factory.newTransformer(AttackTestSupport.streamSource(AttackTestSupport.xsltBody(text))); |
| final StringWriter out = new StringWriter(); |
| transformer.transform(AttackTestSupport.streamSource(AttackTestSupport.xmlBody("ignored")), new StreamResult(out)); |
| return out.toString(); |
| } |
| |
| private static boolean xercesOnClasspath() { |
| try { |
| Class.forName("org.apache.xerces.jaxp.SAXParserFactoryImpl"); |
| return true; |
| } catch (final ClassNotFoundException e) { |
| return false; |
| } |
| } |
| |
| @Test |
| void schemaFactoryReadsFeatureAtCreation() throws Exception { |
| assumeFalse(AttackTestSupport.IS_ANDROID); |
| assumeFeatureSupported(); |
| final SchemaFactory factory = SecureSchemaFactory.newDefaultInstance(); |
| assertFalse(factory.getFeature(FEATURE)); |
| assertFalse(((SecureSchema) factory.newSchema(AttackTestSupport.streamSource(AttackTestSupport.BENIGN_SCHEMA))).overrideDefaultParser); |
| factory.setFeature(FEATURE, true); |
| assertTrue(((SecureSchema) factory.newSchema(AttackTestSupport.streamSource(AttackTestSupport.BENIGN_SCHEMA))).overrideDefaultParser); |
| } |
| |
| @Test |
| void secureReaderFollowsFlag() throws Exception { |
| assumeFalse(AttackTestSupport.IS_ANDROID); |
| final XMLReader pinned = ((SecureXMLReader) SecureSAXParserFactory.newXMLReader(false)).getDelegate(); |
| assertTrue(pinned.getClass().getName().startsWith(JDK_INTERNAL_PREFIX), pinned.getClass().getName()); |
| final XMLReader pluggable = ((SecureXMLReader) SecureSAXParserFactory.newXMLReader(true)).getDelegate(); |
| final XMLReader lookedUp = ((SecureXMLReader) SecureSAXParserFactory.newNSInstance().newSAXParser().getXMLReader()).getDelegate(); |
| assertEquals(lookedUp.getClass(), pluggable.getClass()); |
| if (xercesOnClasspath()) { |
| // The two families genuinely differ only where a third-party parser wins the lookup (the test-jdk-xerces execution). |
| assertNotEquals(pinned.getClass(), pluggable.getClass()); |
| } |
| } |
| |
| @Test |
| void transformerFactoryReadsFeatureAtCreation() throws Exception { |
| assumeFalse(AttackTestSupport.IS_ANDROID); |
| assumeFeatureSupported(); |
| final TransformerFactory factory = SecureTransformerFactory.newDefaultInstance(); |
| assertFalse(factory.getFeature(FEATURE)); |
| assertFalse(((SecureTemplates) factory.newTemplates(AttackTestSupport.streamSource(AttackTestSupport.xsltBody("probe")))).overrideDefaultParser); |
| factory.setFeature(FEATURE, true); |
| assertTrue(((SecureTemplates) factory.newTemplates(AttackTestSupport.streamSource(AttackTestSupport.xsltBody("probe")))).overrideDefaultParser); |
| } |
| |
| @Test |
| // The JDK default TrAX pinned by newDefaultInstance() is XSLTC, which defines the compiled translet class at run time — impossible in a closed-world |
| // native image (the reason the native profile substitutes Xalan). The capture tests above stay enabled: newTemplates never loads the translet. |
| @DisabledInNativeImage |
| void transformSucceedsUnderBothParserFamilies() throws Exception { |
| assumeFalse(AttackTestSupport.IS_ANDROID); |
| assumeFeatureSupported(); |
| final TransformerFactory factory = SecureTransformerFactory.newDefaultInstance(); |
| // Feature false (the JDK's default): stylesheet and source parse through the pinned platform parser. |
| assertTrue(transform(factory, "pinned").contains("pinned")); |
| factory.setFeature(FEATURE, true); |
| // Feature true: same result through the pluggable lookup. |
| assertTrue(transform(factory, "pluggable").contains("pluggable")); |
| } |
| |
| @Test |
| void xPathFactoryReadsFeatureAtCreation() throws Exception { |
| assumeFalse(AttackTestSupport.IS_ANDROID); |
| assumeFeatureSupported(); |
| final XPathFactory factory = SecureXPathFactory.newDefaultInstance(); |
| assertFalse(factory.getFeature(FEATURE)); |
| assertFalse(((SecureXPath) factory.newXPath()).overrideDefaultParser); |
| factory.setFeature(FEATURE, true); |
| assertTrue(((SecureXPath) factory.newXPath()).overrideDefaultParser); |
| } |
| } |