diff --git a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java
index 907fc199f..52cbf303a 100644
--- a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java
+++ b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java
@@ -17,6 +17,7 @@
import org.apache.xmlbeans.*;
import org.apache.xmlbeans.impl.common.InvalidLexicalValueException;
+import org.apache.xmlbeans.impl.common.XMLChar;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
@@ -482,6 +483,13 @@ public static QName lexQName(CharSequence charSeq, NamespaceContext nscontext) {
localname = charSeq.toString();
}
+ if (!prefix.isEmpty() && !XMLChar.isValidNCName(prefix)) {
+ throw new InvalidLexicalValueException("invalid xsd:QName '" + charSeq + "'");
+ }
+ if (!XMLChar.isValidNCName(localname)) {
+ throw new InvalidLexicalValueException("invalid xsd:QName '" + charSeq + "'");
+ }
+
String uri = nscontext.getNamespaceURI(prefix);
if (uri == null) {
diff --git a/src/test/java/misc/checkin/RichParserTests.java b/src/test/java/misc/checkin/RichParserTests.java
index f0c1049e5..1c74de7b1 100755
--- a/src/test/java/misc/checkin/RichParserTests.java
+++ b/src/test/java/misc/checkin/RichParserTests.java
@@ -83,6 +83,30 @@ void testInvalidBase64ThrowsInvalidLexicalValue() throws Exception {
assertThrows(InvalidLexicalValueException.class, () -> attByName.getAttributeBase64Value("", "b"));
}
+ @Test
+ void testInvalidQNameThrowsInvalidLexicalValue() throws Exception {
+ // The localname of an xsd:QName must be an NCName, so a value whose
+ // local part still contains a ':' (or any other non-NCName char) is
+ // outside the lexical space. lexQName resolved the prefix but never
+ // checked the parts, so "p:b:c" came back as QName{uri}b:c instead of
+ // being rejected like the holder validate path does.
+ XMLStreamReaderExt colonInLocal = atFirstStartElement("p:b:c");
+ assertThrows(InvalidLexicalValueException.class, colonInLocal::getQNameValue);
+
+ XMLStreamReaderExt spaceInLocal = atFirstStartElement("b c");
+ assertThrows(InvalidLexicalValueException.class, spaceInLocal::getQNameValue);
+
+ XMLStreamReaderExt emptyLocal = atFirstStartElement("p:");
+ assertThrows(InvalidLexicalValueException.class, emptyLocal::getQNameValue);
+
+ XMLStreamReaderExt attColon = atFirstStartElement("");
+ assertThrows(InvalidLexicalValueException.class, () -> attColon.getAttributeQNameValue(0));
+
+ // a well-formed prefixed QName still resolves
+ XMLStreamReaderExt good = atFirstStartElement("p:good");
+ assertEquals(new QName("urn:x", "good"), good.getQNameValue());
+ }
+
private static XMLStreamReaderExt atFirstStartElement(String xml) throws Exception {
XMLStreamReader xsr = XmlObject.Factory.parse(xml).newXMLStreamReader();
XMLStreamReaderExt ext = new XMLStreamReaderExtImpl(xsr);