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 490bb69b Tighten Max/Min Occurs parsing (#136)
490bb69b is described below
commit 490bb69b683953312763d733402ca572f3261e15
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Aug 27 15:27:22 2026 +0100
Tighten Max/Min Occurs parsing (#136)
---
.../apache/ws/commons/schema/SchemaBuilder.java | 44 ++++----
.../ws/commons/schema/XmlSchemaSerializer.java | 23 ++--
.../src/test/java/tests/OccursParsingTest.java | 119 +++++++++++++++++++++
3 files changed, 157 insertions(+), 29 deletions(-)
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 bbd215fa..5c57d6af 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
@@ -228,35 +228,39 @@ public class SchemaBuilder {
}
long getMaxOccurs(Element el) {
- try {
- if (el.getAttributeNode("maxOccurs") != null) {
- String value = el.getAttribute("maxOccurs");
- if ("unbounded".equals(value)) {
- return Long.MAX_VALUE;
- } else {
- return Long.parseLong(value);
+ if (el.getAttributeNode("maxOccurs") != null) {
+ String value = el.getAttribute("maxOccurs").trim();
+ if ("unbounded".equals(value)) {
+ return Long.MAX_VALUE;
+ }
+ try {
+ long parsed = Long.parseLong(value);
+ if (parsed < 0 || parsed == Long.MAX_VALUE) {
+ throw new XmlSchemaException("Invalid maxOccurs value \""
+ value + "\".");
}
+ return parsed;
+ } catch (java.lang.NumberFormatException e) {
+ throw new XmlSchemaException("Invalid maxOccurs value \"" +
value + "\".", e);
}
- return 1;
- } catch (java.lang.NumberFormatException e) {
- return 1;
}
+ return 1;
}
long getMinOccurs(Element el) {
- try {
- if (el.getAttributeNode("minOccurs") != null) {
- String value = el.getAttribute("minOccurs");
- if ("unbounded".equals(value)) {
- return Long.MAX_VALUE;
- } else {
- return Long.parseLong(value);
+ if (el.getAttributeNode("minOccurs") != null) {
+ String value = el.getAttribute("minOccurs").trim();
+ try {
+ long parsed = Long.parseLong(value);
+ if (parsed < 0 || parsed == Long.MAX_VALUE) {
+ throw new XmlSchemaException("Invalid minOccurs value \""
+ value + "\".");
}
+ return parsed;
+ } catch (java.lang.NumberFormatException e) {
+ throw new XmlSchemaException("Invalid minOccurs value \"" +
value
+ + "\" (\"unbounded\" is only
permitted on maxOccurs).", e);
}
- return 1;
- } catch (java.lang.NumberFormatException e) {
- return 1;
}
+ return 1;
}
/**
diff --git
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaSerializer.java
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaSerializer.java
index 9f5ec5d2..26be3e2c 100644
---
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaSerializer.java
+++
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/XmlSchemaSerializer.java
@@ -2379,18 +2379,23 @@ public class XmlSchemaSerializer {
* @param element
*/
private void serializeMaxMinOccurs(XmlSchemaParticle particle, Element
element) {
- if (particle.getMaxOccurs() < Long.MAX_VALUE
- && (particle.getMaxOccurs() > 1 || particle.getMaxOccurs() == 0)) {
- element.setAttributeNS(null, "maxOccurs", particle.getMaxOccurs()
+ "");
- } else if (particle.getMaxOccurs() == Long.MAX_VALUE) {
+ long maxOccurs = particle.getMaxOccurs();
+ long minOccurs = particle.getMinOccurs();
+ if (minOccurs < 0 || maxOccurs < 0) {
+ throw new XmlSchemaException("Negative occurrence bounds cannot be
serialized.");
+ }
+ if (maxOccurs == Long.MAX_VALUE) {
element.setAttributeNS(null, "maxOccurs", "unbounded");
- // else not serialized
+ } else if (maxOccurs > 1 || maxOccurs == 0) {
+ element.setAttributeNS(null, "maxOccurs", maxOccurs + "");
}
- // 1 is the default and hence not serialized
- // there is no valid case where min occurs can be unbounded!
- if (particle.getMinOccurs() > 1 || particle.getMinOccurs() == 0) {
- element.setAttributeNS(null, "minOccurs", particle.getMinOccurs()
+ "");
+ if (minOccurs == Long.MAX_VALUE) {
+ throw new XmlSchemaException("minOccurs == Long.MAX_VALUE cannot
be serialized: "
+ + "unbounded is only permitted on
maxOccurs.");
+ }
+ if (minOccurs > 1 || minOccurs == 0) {
+ element.setAttributeNS(null, "minOccurs", minOccurs + "");
}
}
diff --git a/xmlschema-core/src/test/java/tests/OccursParsingTest.java
b/xmlschema-core/src/test/java/tests/OccursParsingTest.java
new file mode 100644
index 00000000..46990a77
--- /dev/null
+++ b/xmlschema-core/src/test/java/tests/OccursParsingTest.java
@@ -0,0 +1,119 @@
+/**
+ * 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 javax.xml.namespace.QName;
+
+import org.apache.ws.commons.schema.XmlSchema;
+import org.apache.ws.commons.schema.XmlSchemaCollection;
+import org.apache.ws.commons.schema.XmlSchemaComplexType;
+import org.apache.ws.commons.schema.XmlSchemaElement;
+import org.apache.ws.commons.schema.XmlSchemaException;
+import org.apache.ws.commons.schema.XmlSchemaSequence;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class OccursParsingTest extends Assert {
+
+ private XmlSchema read(String particleAttributes) {
+ String schema =
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
+ + " targetNamespace=\"urn:occurs\">"
+ + "<xs:element name=\"root\"><xs:complexType><xs:sequence>"
+ + "<xs:element name=\"e\" type=\"xs:string\" " +
particleAttributes + "/>"
+ + "</xs:sequence></xs:complexType></xs:element>"
+ + "</xs:schema>";
+ return new XmlSchemaCollection().read(new StringReader(schema));
+ }
+
+ private XmlSchemaElement childElement(XmlSchema schema) {
+ XmlSchemaElement root = schema.getElementByName(new
QName("urn:occurs", "root"));
+ XmlSchemaComplexType type = (XmlSchemaComplexType)root.getSchemaType();
+ XmlSchemaSequence sequence = (XmlSchemaSequence)type.getParticle();
+ return (XmlSchemaElement)sequence.getItems().get(0);
+ }
+
+ private void assertRejected(String particleAttributes, String message) {
+ try {
+ read(particleAttributes);
+ fail(message);
+ } catch (XmlSchemaException expected) {
+ // Expected documented failure surface.
+ }
+ }
+
+ @Test
+ public void testValidOccursValuesStillParse() {
+ XmlSchemaElement element = childElement(read("minOccurs=\"0\"
maxOccurs=\"unbounded\""));
+ assertEquals(0, element.getMinOccurs());
+ assertEquals(Long.MAX_VALUE, element.getMaxOccurs());
+ }
+
+ @Test
+ public void testNonNumericMaxOccursIsRejected() {
+ assertRejected("maxOccurs=\"18446744073709551616\"",
+ "An overflowing maxOccurs value should be rejected, not
coerced to 1.");
+ }
+
+ @Test
+ public void testUnboundedMinOccursIsRejected() {
+ assertRejected("minOccurs=\"unbounded\"",
+ "minOccurs=unbounded should be rejected.");
+ }
+
+ @Test
+ public void testNegativeMinOccursIsRejected() {
+ assertRejected("minOccurs=\"-1\"",
+ "A negative minOccurs should be rejected.");
+ }
+
+ @Test
+ public void testNegativeMaxOccursIsRejected() {
+ assertRejected("maxOccurs=\"-1\"",
+ "A negative maxOccurs should be rejected.");
+ }
+
+ @Test
+ public void testMaxValueLiteralMaxOccursIsRejected() {
+ assertRejected("maxOccurs=\"9223372036854775807\"",
+ "The maxOccurs sentinel literal should be rejected.");
+ }
+
+ @Test
+ public void testMaxValueLiteralMinOccursIsRejected() {
+ assertRejected("minOccurs=\"9223372036854775807\"",
+ "The minOccurs sentinel literal should be rejected.");
+ }
+
+ @Test
+ public void testSentinelMinOccursDoesNotSerializeSilently() throws
Exception {
+ XmlSchema schema = read("minOccurs=\"0\"");
+ childElement(schema).setMinOccurs(Long.MAX_VALUE);
+ try {
+ schema.getSchemaDocument();
+ fail("A sentinel minOccurs must be rejected during
serialization.");
+ } catch (XmlSchemaException expected) {
+ // Expected documented failure surface.
+ }
+ }
+}