blob: c4bb03fc63b10c0bf075699fb7d821345a71ef75 [file]
/*
* 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;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
/**
* Checks that {@code getAssociatedStylesheet} scans for {@code xml-stylesheet} PIs without fetching an external DTD declared in the document prolog.
*
* <p>The PI scan parses the prolog, where a {@code DOCTYPE} with an external subset is processed before the root element. On Apache Xalan the scan runs on a
* reader the engine provisions itself, ignoring a hardened reader passed in a {@link javax.xml.transform.sax.SAXSource} (XALANJ-2849); the wrapper works around
* that by handing Xalan a {@code DOMSource} it pre-parsed through a hardened {@code DocumentBuilder}. The JDK's XSLTC honors the hardened reader directly. Either
* way the external DTD resolves to empty instead of being fetched. Tagged {@code trax}, so it runs on the stock JDK, Apache Xalan, Saxon, and the Android
* runtime.</p>
*/
@Tag("trax")
class AssociatedStylesheetTest {
private static TransformerFactory hardenedFactory() {
final TransformerFactory factory = XmlFactories.newTransformerFactory();
factory.setErrorListener(AttackTestSupport.STRICT_REPORTER);
return factory;
}
@Test
void hardenedGetAssociatedStylesheetIgnoresExternalDtd() throws TransformerConfigurationException {
// The prolog declares an unreachable external DTD; the hardened parse resolves it to empty rather than fetching it, so the PI scan completes and finds
// the stylesheet instead of throwing on a fetch. (The returned Source's shape is engine-specific: XSLTC and Xalan point it at included.xsl, while Saxon
// resolves the href through its own floor and returns an empty source; both mean the scan ran without fetching the DTD.)
final Source associated = hardenedFactory()
.getAssociatedStylesheet(AttackTestSupport.resourceSource("associated-stylesheet.xml"), null, null, null);
assertAssociatedStylesheet(associated);
}
@Test
void hardenedGetAssociatedStylesheetReturnsStylesheet() throws TransformerConfigurationException {
// Positive control: a plain document with no DOCTYPE resolves its xml-stylesheet PI end to end.
final Source associated = hardenedFactory()
.getAssociatedStylesheet(AttackTestSupport.resourceSource("associated-stylesheet-plain.xml"), null, null, null);
assertAssociatedStylesheet(associated);
}
/** The PI was found (non-null); where the engine exposes a system id, it points at the declared stylesheet. */
private static void assertAssociatedStylesheet(final Source associated) {
assertNotNull(associated, "expected the associated stylesheet PI to be found");
if (associated.getSystemId() != null) {
assertTrue(associated.getSystemId().endsWith("included.xsl"), "unexpected associated stylesheet: " + associated.getSystemId());
}
}
@Test
void unconfiguredGetAssociatedStylesheetFetchesExternalDtd() {
// Leak/discrimination control: the unconfigured engine attempts to fetch the unreachable external DTD and fails. Android's KXmlParser does not fetch
// external DTDs, so it has nothing to demonstrate here.
Assumptions.assumeFalse(AttackTestSupport.IS_ANDROID, "Android's KXmlParser does not fetch external DTDs");
assertThrows(TransformerConfigurationException.class, () -> TransformerFactory.newInstance()
.getAssociatedStylesheet(AttackTestSupport.resourceSource("associated-stylesheet.xml"), null, null, null));
}
}