blob: 5fa7c14641127f87a2187b7087be32a93c64249d [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
*
* 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 tests;
import java.io.StringReader;
import org.apache.ws.commons.schema.XmlSchemaCollection;
import org.apache.ws.commons.schema.XmlSchemaException;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* The JDK parser unwinds nested entities recursively, so a chain of internal entities each
* referring to the next must be refused by the entity-expansion limit before it can exhaust the
* thread stack.
*/
public class EntityExpansionLimitTest extends Assert {
private static final String ENTITY_EXPANSION_LIMIT = "jdk.xml.entityExpansionLimit";
private String savedLimit;
@Before
public void saveLimit() {
savedLimit = System.getProperty(ENTITY_EXPANSION_LIMIT);
System.clearProperty(ENTITY_EXPANSION_LIMIT);
}
@After
public void restoreLimit() {
if (savedLimit == null) {
System.clearProperty(ENTITY_EXPANSION_LIMIT);
} else {
System.setProperty(ENTITY_EXPANSION_LIMIT, savedLimit);
}
}
/**
* On a 256 KB stack a chain of 3,000 overflowed inside the JDK parser on JDK 8 to 25; at the
* default stack size a chain of about 30,000 did.
*/
@Test
public void testEntityChainIsRefusedOnASmallStack() throws Exception {
final Throwable[] thrown = new Throwable[1];
Thread reader = new Thread(null, new Runnable() {
public void run() {
try {
new XmlSchemaCollection().read(new StringReader(chainedSchema(3000)));
} catch (Throwable t) {
thrown[0] = t;
}
}
}, "entity-chain", 256 * 1024);
reader.start();
reader.join();
assertTrue("Expected XmlSchemaException, got " + thrown[0],
thrown[0] instanceof XmlSchemaException);
assertTrue(thrown[0].getMessage(), thrown[0].getMessage().contains("too many entities"));
}
@Test
public void testEntityChainWithinTheLimitStillParses() {
assertNotNull(new XmlSchemaCollection().read(new StringReader(chainedSchema(900))));
}
@Test
public void testLowerEntityExpansionLimitIsKept() {
System.setProperty(ENTITY_EXPANSION_LIMIT, "5");
try {
new XmlSchemaCollection().read(new StringReader(chainedSchema(10)));
fail("Ten expansions should be refused under a limit of five.");
} catch (XmlSchemaException expected) {
assertTrue(expected.getMessage(), expected.getMessage().contains("too many entities"));
}
}
private static String chainedSchema(int length) {
StringBuilder schema = new StringBuilder("<!DOCTYPE xs:schema [");
for (int i = 1; i < length; i++) {
schema.append("<!ENTITY e").append(i).append(" '&e").append(i + 1).append(";'>");
}
schema.append("<!ENTITY e").append(length).append(" 'x'>]>")
.append("<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">")
.append("<xs:annotation><xs:documentation>&e1;</xs:documentation></xs:annotation>")
.append("</xs:schema>");
return schema.toString();
}
}