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.
+        }
+    }
+}

Reply via email to