This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-xmlschema.git
The following commit(s) were added to refs/heads/master by this push:
new e623a26a Bound the nesting depth of annotation markup (#167)
e623a26a is described below
commit e623a26af6169c73eafbc410cc4c117afa2dcb2a
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Sep 24 10:59:36 2026 +0100
Bound the nesting depth of annotation markup (#167)
---
.../apache/ws/commons/schema/SchemaBuilder.java | 46 ++++++++++++++++++++
.../src/test/java/tests/NestingDepthLimitTest.java | 50 ++++++++++++++++++++++
2 files changed, 96 insertions(+)
diff --git
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java
index e7609d9f..d038cad2 100644
---
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java
+++
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/SchemaBuilder.java
@@ -151,6 +151,50 @@ public class SchemaBuilder {
collection.builderNestingDepth--;
}
+ /**
+ * Refuse appinfo or documentation markup nested deeper than the structural
+ * bound allows. The markup is not schema structure, so the handlers never
+ * count it, but it is copied with a recursive cloneNode on this same
thread
+ * stack, so it counts toward the same bound. Measured iteratively, as the
+ * point is to avoid recursing over it.
+ */
+ private void checkMarkupDepth(Element content) {
+ int depth = 0;
+ int maxDepth = 0;
+ Node node = content.getFirstChild();
+ if (node != null) {
+ depth = 1;
+ }
+ while (node != null) {
+ if (depth > maxDepth) {
+ maxDepth = depth;
+ if (collection.builderNestingDepth + maxDepth >
MAX_NESTING_DEPTH) {
+ throw new XmlSchemaException("The markup of an annotation
is nested more than "
+ + MAX_NESTING_DEPTH + "
levels deep, counting the"
+ + " schema structure around
it; refusing to build it."
+ + " The limit may be changed
with the"
+ + "
org.apache.ws.commons.schema.maxNestingDepth"
+ + " system property.");
+ }
+ }
+ if (node.getFirstChild() != null) {
+ node = node.getFirstChild();
+ depth++;
+ continue;
+ }
+ while (node != null && node.getNextSibling() == null) {
+ node = node.getParentNode();
+ depth--;
+ if (node == content) {
+ node = null;
+ }
+ }
+ if (node != null) {
+ node = node.getNextSibling();
+ }
+ }
+ }
+
private static int getIntProperty(String name, int defaultValue) {
try {
Integer value = Integer.getInteger(name);
@@ -306,6 +350,7 @@ public class SchemaBuilder {
*/
XmlSchemaAppInfo handleAppInfo(Element content) {
XmlSchemaAppInfo appInfo = new XmlSchemaAppInfo();
+ checkMarkupDepth(content);
NodeList markup = new DocumentFragmentNodeList(content);
if (!content.hasAttribute("source") && markup.getLength() == 0) {
@@ -418,6 +463,7 @@ public class SchemaBuilder {
// to collection
XmlSchemaDocumentation handleDocumentation(Element content) {
XmlSchemaDocumentation documentation = new XmlSchemaDocumentation();
+ checkMarkupDepth(content);
List<Node> markup = getChildren(content);
if (!content.hasAttribute("source") &&
!content.hasAttribute("xml:lang") && markup == null) {
diff --git a/xmlschema-core/src/test/java/tests/NestingDepthLimitTest.java
b/xmlschema-core/src/test/java/tests/NestingDepthLimitTest.java
index d9323542..e9c70811 100644
--- a/xmlschema-core/src/test/java/tests/NestingDepthLimitTest.java
+++ b/xmlschema-core/src/test/java/tests/NestingDepthLimitTest.java
@@ -129,6 +129,56 @@ public class NestingDepthLimitTest extends Assert {
assertNotNull(schema);
}
+ /**
+ * Annotation markup is copied with a recursive cloneNode, so deep markup
+ * inside xs:documentation or xs:appinfo must hit the same bound rather
+ * than overflow the stack.
+ */
+ @Test
+ public void testDeeplyNestedDocumentationMarkupIsRejected() throws
Exception {
+ assertMarkupRejected("documentation");
+ }
+
+ @Test
+ public void testDeeplyNestedAppInfoMarkupIsRejected() throws Exception {
+ assertMarkupRejected("appinfo");
+ }
+
+ @Test
+ public void testReasonablyNestedMarkupStillParses() throws Exception {
+ XmlSchemaCollection collection = new XmlSchemaCollection();
+ XmlSchema schema = collection.read(new
StringReader(buildAnnotatedSchema("documentation", 50)));
+ assertNotNull(schema);
+ schema = collection.read(new
StringReader(buildAnnotatedSchema("appinfo", 50)));
+ assertNotNull(schema);
+ }
+
+ private void assertMarkupRejected(String kind) {
+ XmlSchemaCollection collection = new XmlSchemaCollection();
+ try {
+ collection.read(new StringReader(buildAnnotatedSchema(kind,
5000)));
+ fail("xs:" + kind + " markup nested 5000 levels deep should be
rejected.");
+ } catch (XmlSchemaException expected) {
+ assertTrue(expected.getMessage().contains("nested"));
+ }
+ }
+
+ private String buildAnnotatedSchema(String kind, int depth) {
+ StringBuilder schema = new StringBuilder();
+ schema.append("<xs:schema
xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"")
+ .append("
targetNamespace=\"urn:markup-").append(kind).append("\">");
+ schema.append("<xs:annotation><xs:").append(kind).append(">");
+ for (int i = 0; i < depth; i++) {
+ schema.append("<a>");
+ }
+ schema.append("text");
+ for (int i = 0; i < depth; i++) {
+ schema.append("</a>");
+ }
+
schema.append("</xs:").append(kind).append("></xs:annotation></xs:schema>");
+ return schema.toString();
+ }
+
private String buildNestedSchema(int depth) {
return buildNestedSchema(depth, "urn:nesting");
}