Skip to content

Commit

Permalink
dom4j#48 Validate QName inputs - throw IllegalArgumentException when …
Browse files Browse the repository at this point in the history
…qualified name contains disallowed character.
  • Loading branch information
FilipJirsak authored and bmccarthy-atlassian committed Oct 21, 2019
1 parent 3772e6c commit bf0e58e
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/java/org/dom4j/Namespace.java
Expand Up @@ -51,6 +51,10 @@ public class Namespace extends AbstractNode {
public Namespace(String prefix, String uri) {
this.prefix = (prefix != null) ? prefix : "";
this.uri = (uri != null) ? uri : "";

if (!this.prefix.isEmpty()) {
QName.validateNCName(this.prefix);
}
}

/**
Expand Down
101 changes: 101 additions & 0 deletions src/java/org/dom4j/QName.java
Expand Up @@ -11,6 +11,7 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.regex.Pattern;

import org.dom4j.tree.QNameCache;
import org.dom4j.util.SingletonStrategy;
Expand All @@ -23,11 +24,86 @@
* </p>
*
* @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
* @author Filip Jirsák
*/
public class QName implements Serializable {
/** The Singleton instance */
private static SingletonStrategy singleton = null;

/**
* {@code NameStartChar} without colon.
*
* <pre>NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]</pre>
*
* @see <a href="https://www.w3.org/TR/xml/#sec-common-syn">XML 1.0 – 2.3 Common Syntactic Constructs</a>
* @see <a href="https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-common-syn">XML 1.1 – 2.3 Common Syntactic Constructs</a>
*/
private static final String NAME_START_CHAR = "_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD";

/**
* {@code NameChar} without colon.
*
* <pre>NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]</pre>
*
* @see <a href="https://www.w3.org/TR/xml/#sec-common-syn">XML 1.0 – 2.3 Common Syntactic Constructs</a>
* @see <a href="https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-common-syn">XML 1.1 – 2.3 Common Syntactic Constructs</a>
*/
private static final String NAME_CHAR = NAME_START_CHAR + "-.0-9\u00B7\u0300-\u036F\u203F-\u2040";

/**
* {@code NCName}
*
* <pre>
* NCName ::= NCNameStartChar NCNameChar* (An XML Name, minus the ":")
* NCNameChar ::= NameChar -':'
* NCNameStartChar ::= NameStartChar -':'
* </pre>
*
* @see <a href="https://www.w3.org/TR/xml-names/#ns-qualnames">Namespaces in XML 1.0 – 4 Qualified Names</a>
* @see <a href="https://www.w3.org/TR/2006/REC-xml-names11-20060816/#ns-qualnames">Namespaces in XML 1.1 – 4 Qualified Names</a>
*/
private static final String NCNAME = "["+NAME_START_CHAR+"]["+NAME_CHAR+"]*";

/**
* Regular expression for {@code Name} (with colon).
*
* <pre>Name ::= NameStartChar (NameChar)*</pre>
*
* @see <a href="https://www.w3.org/TR/xml/#sec-common-syn">XML 1.0 – 2.3 Common Syntactic Constructs</a>
* @see <a href="https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-common-syn">XML 1.1 – 2.3 Common Syntactic Constructs</a>
*/
private static final Pattern RE_NAME = Pattern.compile("[:"+NAME_START_CHAR+"][:"+NAME_CHAR+"]*");

/**
* Regular expression for {@code NCName}.
*
* <pre>
* NCName ::= NCNameStartChar NCNameChar* (An XML Name, minus the ":")
* NCNameChar ::= NameChar -':'
* NCNameStartChar ::= NameStartChar -':'
* </pre>
*
* @see <a href="https://www.w3.org/TR/xml-names/#ns-qualnames">Namespaces in XML 1.0 – 4 Qualified Names</a>
* @see <a href="https://www.w3.org/TR/2006/REC-xml-names11-20060816/#ns-qualnames">Namespaces in XML 1.1 – 4 Qualified Names</a>
*/
private static final Pattern RE_NCNAME = Pattern.compile(NCNAME);

/**
* Regular expression for {@code QName}.
*
* <pre>
* QName ::= PrefixedName | UnprefixedName
* PrefixedName ::= Prefix ':' LocalPart
* UnprefixedName ::= LocalPart
* Prefix ::= NCName
* LocalPart ::= NCName
* </pre>
*
* @see <a href="https://www.w3.org/TR/xml-names/#ns-qualnames">Namespaces in XML 1.0 – 4 Qualified Names</a>
* @see <a href="https://www.w3.org/TR/2006/REC-xml-names11-20060816/#ns-qualnames">Namespaces in XML 1.1 – 4 Qualified Names</a>
*/
private static final Pattern RE_QNAME = Pattern.compile("(?:"+NCNAME+":)?"+NCNAME);

static {
try {
String defaultSingletonClass = "org.dom4j.util.SimpleSingleton";
Expand Down Expand Up @@ -73,13 +149,20 @@ public QName(String name, Namespace namespace) {
this.name = (name == null) ? "" : name;
this.namespace = (namespace == null) ? Namespace.NO_NAMESPACE
: namespace;
if (this.namespace.equals(Namespace.NO_NAMESPACE)) {
validateName(this.name);
} else {
validateNCName(this.name);
}
}

public QName(String name, Namespace namespace, String qualifiedName) {
this.name = (name == null) ? "" : name;
this.qualifiedName = qualifiedName;
this.namespace = (namespace == null) ? Namespace.NO_NAMESPACE
: namespace;
validateNCName(this.name);
validateQName(this.qualifiedName);
}

public static QName get(String name) {
Expand Down Expand Up @@ -253,6 +336,24 @@ private static QNameCache getCache() {
QNameCache cache = (QNameCache) singleton.instance();
return cache;
}

private static void validateName(String name) {
if (!RE_NAME.matcher(name).matches()) {
throw new IllegalArgumentException(String.format("Illegal character in name: '%s'.", name));
}
}

protected static void validateNCName(String ncname) {
if (!RE_NCNAME.matcher(ncname).matches()) {
throw new IllegalArgumentException(String.format("Illegal character in local name: '%s'.", ncname));
}
}

private static void validateQName(String qname) {
if (!RE_QNAME.matcher(qname).matches()) {
throw new IllegalArgumentException(String.format("Illegal character in qualified name: '%s'.", qname));
}
}
}


Expand Down
2 changes: 2 additions & 0 deletions src/java/org/dom4j/tree/QNameCache.java
Expand Up @@ -164,6 +164,8 @@ public QName get(String qualifiedName, String uri) {

if (index < 0) {
return get(qualifiedName, Namespace.get(uri));
} else if (index == 0){
throw new IllegalArgumentException("Qualified name cannot start with ':'.");
} else {
String name = qualifiedName.substring(index + 1);
String prefix = qualifiedName.substring(0, index);
Expand Down
78 changes: 78 additions & 0 deletions src/test/org/dom4j/AllowedCharsTest.java
@@ -0,0 +1,78 @@
package org.dom4j;

import org.testng.annotations.Test;

/**
* @author Filip Jirsák
*/
public class AllowedCharsTest {
@Test
public void localName() {
QName.get("element");
QName.get(":element");
QName.get("elem:ent");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void localNameFail() {
QName.get("!element");
}

@Test
public void qname() {
QName.get("element", "http://example.com/namespace");
QName.get("ns:element", "http://example.com/namespace");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void qnameFail1() {
QName.get("ns:elem:ent", "http://example.com/namespace");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void qnameFail2() {
QName.get(":nselement", "http://example.com/namespace");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void createElementLT() {
DocumentHelper.createElement("element<name");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void createElementGT() {
DocumentHelper.createElement("element>name");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void createElementAmpersand() {
DocumentHelper.createElement("element&name");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void addElement() {
Element root = DocumentHelper.createElement("root");
root.addElement("element>name");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void addElementQualified() {
Element root = DocumentHelper.createElement("root");
root.addElement("element>name", "http://example.com/namespace");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void addElementQualifiedPrefix() {
Element root = DocumentHelper.createElement("root");
root.addElement("ns:element>name", "http://example.com/namespace");
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void addElementPrefix() {
Element root = DocumentHelper.createElement("root");
root.addElement("ns>:element", "http://example.com/namespace");
}

//TODO It is illegal to create element or attribute with namespace prefix and empty namespace IRI.
//See https://www.w3.org/TR/2006/REC-xml-names11-20060816/#scoping
}

0 comments on commit bf0e58e

Please sign in to comment.