|
22 | 22 | package org.exist.xquery.update;
|
23 | 23 |
|
24 | 24 | import org.exist.EXistException;
|
| 25 | +import org.exist.Namespaces; |
25 | 26 | import org.exist.collections.triggers.TriggerException;
|
| 27 | +import org.exist.dom.NodeListImpl; |
| 28 | +import org.exist.dom.QName; |
26 | 29 | import org.exist.dom.persistent.DocumentImpl;
|
| 30 | +import org.exist.dom.persistent.ElementImpl; |
27 | 31 | import org.exist.dom.persistent.NodeImpl;
|
28 |
| -import org.exist.dom.NodeListImpl; |
29 | 32 | import org.exist.dom.persistent.StoredNode;
|
30 | 33 | import org.exist.security.Permission;
|
31 | 34 | import org.exist.security.PermissionDeniedException;
|
32 | 35 | import org.exist.storage.NotificationService;
|
33 | 36 | import org.exist.storage.UpdateListener;
|
34 | 37 | import org.exist.storage.txn.Txn;
|
35 | 38 | import org.exist.util.LockException;
|
36 |
| -import org.exist.xquery.Dependency; |
37 |
| -import org.exist.xquery.Expression; |
38 |
| -import org.exist.xquery.Profiler; |
39 |
| -import org.exist.xquery.XPathException; |
40 |
| -import org.exist.xquery.XPathUtil; |
41 |
| -import org.exist.xquery.XQueryContext; |
| 39 | +import org.exist.xquery.*; |
42 | 40 | import org.exist.xquery.util.Error;
|
43 | 41 | import org.exist.xquery.util.ExpressionDumper;
|
44 | 42 | import org.exist.xquery.util.Messages;
|
45 |
| -import org.exist.xquery.value.Item; |
46 |
| -import org.exist.xquery.value.NodeValue; |
47 |
| -import org.exist.xquery.value.Sequence; |
48 |
| -import org.exist.xquery.value.SequenceIterator; |
49 |
| -import org.exist.xquery.value.StringValue; |
50 |
| -import org.exist.xquery.value.Type; |
51 |
| -import org.exist.xquery.value.ValueSequence; |
52 |
| -import org.w3c.dom.Attr; |
| 43 | +import org.exist.xquery.value.*; |
| 44 | +import org.w3c.dom.NamedNodeMap; |
| 45 | +import org.w3c.dom.Node; |
53 | 46 | import org.w3c.dom.NodeList;
|
| 47 | + |
| 48 | +import javax.xml.XMLConstants; |
| 49 | + |
54 | 50 | /**
|
55 | 51 | * @author wolf
|
56 | 52 | *
|
@@ -134,20 +130,22 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
|
134 | 130 |
|
135 | 131 | //start a transaction
|
136 | 132 | try (final Txn transaction = getTransaction()) {
|
137 |
| - final StoredNode[] ql = selectAndLock(transaction, inSeq); |
| 133 | + final StoredNode<?>[] ql = selectAndLock(transaction, inSeq); |
138 | 134 | final NotificationService notifier = context.getBroker().getBrokerPool().getNotificationService();
|
139 | 135 | final NodeList contentList = seq2nodeList(contentSeq);
|
140 |
| - for (final StoredNode node : ql) { |
| 136 | + for (final StoredNode<?> node : ql) { |
141 | 137 | final DocumentImpl doc = node.getOwnerDocument();
|
142 | 138 | if (!doc.getPermissions().validate(context.getSubject(), Permission.WRITE)) {
|
143 | 139 | throw new PermissionDeniedException("User '" + context.getSubject().getName() + "' does not have permission to write to the document '" + doc.getDocumentURI() + "'!");
|
144 | 140 | }
|
145 | 141 |
|
146 | 142 | //update the document
|
147 | 143 | if (mode == INSERT_APPEND) {
|
| 144 | + validateNonDefaultNamespaces(contentList, node); |
148 | 145 | node.appendChildren(transaction, contentList, -1);
|
149 | 146 | } else {
|
150 |
| - final NodeImpl parent = (NodeImpl) getParent(node); |
| 147 | + final NodeImpl<?> parent = (NodeImpl<?>) getParent(node); |
| 148 | + validateNonDefaultNamespaces(contentList, parent); |
151 | 149 | switch (mode) {
|
152 | 150 | case INSERT_BEFORE:
|
153 | 151 | parent.insertBefore(transaction, contentList, node);
|
@@ -180,6 +178,64 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
|
180 | 178 | return Sequence.EMPTY_SEQUENCE;
|
181 | 179 | }
|
182 | 180 |
|
| 181 | + private QName nodeQName(Node node) { |
| 182 | + final String ns = node.getNamespaceURI(); |
| 183 | + final String prefix = (Namespaces.XML_NS.equals(ns) ? XMLConstants.XML_NS_PREFIX : node.getPrefix()); |
| 184 | + String name = node.getLocalName(); |
| 185 | + if(name == null) { |
| 186 | + name = node.getNodeName(); |
| 187 | + } |
| 188 | + return new QName(name, ns, prefix); |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Generate a namespace attribute as a companion to some other node (element or attribute) |
| 193 | + * if the namespace of the other node is explicit |
| 194 | + * |
| 195 | + * @param parentElement target of the insertion (persistent) |
| 196 | + * @param insertNode node to be inserted (in-memory) |
| 197 | + * @throws XPathException on an unresolvable namespace conflict |
| 198 | + */ |
| 199 | + private void validateNonDefaultNamespaceNode(final ElementImpl parentElement, final Node insertNode) throws XPathException { |
| 200 | + |
| 201 | + final QName qName = nodeQName(insertNode); |
| 202 | + final String prefix = qName.getPrefix(); |
| 203 | + if (prefix != null && qName.hasNamespace()) { |
| 204 | + final String existingNamespaceURI = parentElement.lookupNamespaceURI(prefix); |
| 205 | + if (!XMLConstants.NULL_NS_URI.equals(existingNamespaceURI) && !qName.getNamespaceURI().equals(existingNamespaceURI)) { |
| 206 | + throw new XPathException(this, ErrorCodes.XUDY0023, |
| 207 | + "The namespace mapping of " + prefix + " -> " + |
| 208 | + qName.getNamespaceURI() + |
| 209 | + " would conflict with the existing namespace mapping of " + |
| 210 | + prefix + " -> " + existingNamespaceURI); |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + validateNonDefaultNamespaces(insertNode.getChildNodes(), parentElement); |
| 215 | + final NamedNodeMap attributes = insertNode.getAttributes(); |
| 216 | + for (int i = 0; attributes != null && i < attributes.getLength(); i++) { |
| 217 | + validateNonDefaultNamespaceNode(parentElement, attributes.item(i)); |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + /** |
| 222 | + * Validate that a list of nodes (intended for insertion) have no namespace conflicts |
| 223 | + * |
| 224 | + * @param nodeList nodes to check |
| 225 | + * @param parent the position into which the nodes are being inserted |
| 226 | + * @throws XPathException if a node has a namespace conflict |
| 227 | + */ |
| 228 | + private <T extends NodeImpl<T>> void validateNonDefaultNamespaces(final NodeList nodeList, final NodeImpl<T> parent) throws XPathException { |
| 229 | + if (parent instanceof ElementImpl) { |
| 230 | + final ElementImpl parentAsElement = (ElementImpl) parent; |
| 231 | + for (int i = 0; i < nodeList.getLength(); i++) { |
| 232 | + final Node node = nodeList.item(i); |
| 233 | + |
| 234 | + validateNonDefaultNamespaceNode(parentAsElement, node); |
| 235 | + } |
| 236 | + } |
| 237 | + } |
| 238 | + |
183 | 239 | private NodeList seq2nodeList(Sequence contentSeq) throws XPathException {
|
184 | 240 | final NodeListImpl nl = new NodeListImpl();
|
185 | 241 | for (final SequenceIterator i = contentSeq.iterate(); i.hasNext(); ) {
|
|
0 commit comments