Switching to use CXF's StaxUtils for DOM parsing. Still having problems with DTD parsing. git-svn-id: https://svn.apache.org/repos/asf/santuario/xml-security-java/branches/staxutils@1847262 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/xml/security/c14n/Canonicalizer.java b/src/main/java/org/apache/xml/security/c14n/Canonicalizer.java index 17c60de..7565799 100644 --- a/src/main/java/org/apache/xml/security/c14n/Canonicalizer.java +++ b/src/main/java/org/apache/xml/security/c14n/Canonicalizer.java
@@ -26,6 +26,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import javax.xml.stream.XMLStreamException; + import org.apache.xml.security.c14n.implementations.Canonicalizer11_OmitComments; import org.apache.xml.security.c14n.implementations.Canonicalizer11_WithComments; import org.apache.xml.security.c14n.implementations.Canonicalizer20010315ExclOmitComments; @@ -246,10 +248,11 @@ * @throws java.io.IOException * @throws javax.xml.parsers.ParserConfigurationException * @throws org.xml.sax.SAXException + * @throws XMLStreamException */ public byte[] canonicalize(byte[] inputBytes) throws javax.xml.parsers.ParserConfigurationException, - java.io.IOException, org.xml.sax.SAXException, CanonicalizationException { + java.io.IOException, org.xml.sax.SAXException, CanonicalizationException, XMLStreamException { Document document = null; try (InputStream bais = new ByteArrayInputStream(inputBytes)) { InputSource in = new InputSource(bais);
diff --git a/src/main/java/org/apache/xml/security/c14n/CanonicalizerSpi.java b/src/main/java/org/apache/xml/security/c14n/CanonicalizerSpi.java index be193eb..9734bb5 100644 --- a/src/main/java/org/apache/xml/security/c14n/CanonicalizerSpi.java +++ b/src/main/java/org/apache/xml/security/c14n/CanonicalizerSpi.java
@@ -22,6 +22,8 @@ import java.io.OutputStream; import java.util.Set; +import javax.xml.stream.XMLStreamException; + import org.apache.xml.security.utils.XMLUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -48,10 +50,11 @@ * @throws java.io.IOException * @throws javax.xml.parsers.ParserConfigurationException * @throws org.xml.sax.SAXException + * @throws XMLStreamException */ public byte[] engineCanonicalize(byte[] inputBytes) throws javax.xml.parsers.ParserConfigurationException, java.io.IOException, - org.xml.sax.SAXException, CanonicalizationException { + org.xml.sax.SAXException, CanonicalizationException, XMLStreamException { Document document = null; try (java.io.InputStream bais = new ByteArrayInputStream(inputBytes)) {
diff --git a/src/main/java/org/apache/xml/security/c14n/implementations/Canonicalizer20010315.java b/src/main/java/org/apache/xml/security/c14n/implementations/Canonicalizer20010315.java index 03c0548..954f640 100644 --- a/src/main/java/org/apache/xml/security/c14n/implementations/Canonicalizer20010315.java +++ b/src/main/java/org/apache/xml/security/c14n/implementations/Canonicalizer20010315.java
@@ -26,6 +26,7 @@ import java.util.TreeSet; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.helper.C14nHelper; @@ -296,7 +297,7 @@ } protected void circumventBugIfNeeded(XMLSignatureInput input) - throws CanonicalizationException, ParserConfigurationException, IOException, SAXException { + throws CanonicalizationException, ParserConfigurationException, IOException, SAXException, XMLStreamException { if (!input.isNeedsToBeExpanded()) { return; }
diff --git a/src/main/java/org/apache/xml/security/c14n/implementations/Canonicalizer20010315Excl.java b/src/main/java/org/apache/xml/security/c14n/implementations/Canonicalizer20010315Excl.java index fda565e..ef34f68 100644 --- a/src/main/java/org/apache/xml/security/c14n/implementations/Canonicalizer20010315Excl.java +++ b/src/main/java/org/apache/xml/security/c14n/implementations/Canonicalizer20010315Excl.java
@@ -25,6 +25,7 @@ import java.util.SortedSet; import java.util.TreeSet; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.helper.C14nHelper; @@ -337,7 +338,7 @@ protected void circumventBugIfNeeded(XMLSignatureInput input) throws CanonicalizationException, ParserConfigurationException, - IOException, SAXException { + IOException, SAXException, XMLStreamException { if (!input.isNeedsToBeExpanded() || inclusiveNSSet.isEmpty()) { return; }
diff --git a/src/main/java/org/apache/xml/security/c14n/implementations/CanonicalizerBase.java b/src/main/java/org/apache/xml/security/c14n/implementations/CanonicalizerBase.java index 8a329cf..1bd60d4 100644 --- a/src/main/java/org/apache/xml/security/c14n/implementations/CanonicalizerBase.java +++ b/src/main/java/org/apache/xml/security/c14n/implementations/CanonicalizerBase.java
@@ -31,6 +31,7 @@ import java.util.Set; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.CanonicalizerSpi; @@ -160,11 +161,7 @@ } } return null; - } catch (ParserConfigurationException ex) { - throw new CanonicalizationException(ex); - } catch (IOException ex) { - throw new CanonicalizationException(ex); - } catch (SAXException ex) { + } catch (ParserConfigurationException | IOException | SAXException | XMLStreamException ex) { throw new CanonicalizationException(ex); } } @@ -654,7 +651,7 @@ throws CanonicalizationException, DOMException, IOException; abstract void circumventBugIfNeeded(XMLSignatureInput input) - throws CanonicalizationException, ParserConfigurationException, IOException, SAXException; + throws CanonicalizationException, ParserConfigurationException, IOException, SAXException, XMLStreamException; /** * Outputs an Attribute to the internal Writer.
diff --git a/src/main/java/org/apache/xml/security/encryption/DocumentSerializer.java b/src/main/java/org/apache/xml/security/encryption/DocumentSerializer.java index a35ff71..4c17e8b 100644 --- a/src/main/java/org/apache/xml/security/encryption/DocumentSerializer.java +++ b/src/main/java/org/apache/xml/security/encryption/DocumentSerializer.java
@@ -24,6 +24,7 @@ import java.io.StringReader; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.utils.XMLUtils; import org.w3c.dom.Document; @@ -89,12 +90,8 @@ child = fragElt.getFirstChild(); } return result; - } catch (SAXException se) { - throw new XMLEncryptionException(se); - } catch (ParserConfigurationException pce) { - throw new XMLEncryptionException(pce); - } catch (IOException ioe) { - throw new XMLEncryptionException(ioe); + } catch (ParserConfigurationException | IOException | SAXException | XMLStreamException ex) { + throw new XMLEncryptionException(ex); } }
diff --git a/src/main/java/org/apache/xml/security/keys/keyresolver/KeyResolverSpi.java b/src/main/java/org/apache/xml/security/keys/keyresolver/KeyResolverSpi.java index f5456a6..f55243c 100644 --- a/src/main/java/org/apache/xml/security/keys/keyresolver/KeyResolverSpi.java +++ b/src/main/java/org/apache/xml/security/keys/keyresolver/KeyResolverSpi.java
@@ -28,6 +28,7 @@ import javax.crypto.SecretKey; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.keys.storage.StorageResolver; import org.apache.xml.security.utils.XMLUtils; @@ -273,11 +274,7 @@ try (InputStream is = new ByteArrayInputStream(bytes)) { Document doc = XMLUtils.read(is, secureValidation); return doc.getDocumentElement(); - } catch (SAXException ex) { - throw new KeyResolverException(ex); - } catch (IOException ex) { - throw new KeyResolverException(ex); - } catch (ParserConfigurationException ex) { + } catch (ParserConfigurationException | IOException | SAXException | XMLStreamException ex) { throw new KeyResolverException(ex); } }
diff --git a/src/main/java/org/apache/xml/security/keys/keyresolver/implementations/RetrievalMethodResolver.java b/src/main/java/org/apache/xml/security/keys/keyresolver/implementations/RetrievalMethodResolver.java index 611e494..7f557f9 100644 --- a/src/main/java/org/apache/xml/security/keys/keyresolver/implementations/RetrievalMethodResolver.java +++ b/src/main/java/org/apache/xml/security/keys/keyresolver/implementations/RetrievalMethodResolver.java
@@ -32,6 +32,7 @@ import java.util.Set; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.exceptions.XMLSecurityException; @@ -126,7 +127,9 @@ LOG.debug("ParserConfigurationException", e); } catch (SAXException e) { LOG.debug("SAXException", e); - } + } catch (XMLStreamException e) { + LOG.debug("XMLStreamException", e); + } return null; } @@ -184,6 +187,8 @@ LOG.debug("ParserConfigurationException", e); } catch (SAXException e) { LOG.debug("SAXException", e); + } catch (XMLStreamException e) { + LOG.debug("XMLStreamException", e); } return null; } @@ -234,7 +239,7 @@ private static Element obtainReferenceElement(XMLSignatureInput resource, boolean secureValidation) throws CanonicalizationException, ParserConfigurationException, - IOException, SAXException, KeyResolverException { + IOException, SAXException, KeyResolverException, XMLStreamException { Element e; if (resource.isElement()){ e = (Element) resource.getSubNode();
diff --git a/src/main/java/org/apache/xml/security/signature/Manifest.java b/src/main/java/org/apache/xml/security/signature/Manifest.java index 7eb83e5..240d93f 100644 --- a/src/main/java/org/apache/xml/security/signature/Manifest.java +++ b/src/main/java/org/apache/xml/security/signature/Manifest.java
@@ -30,6 +30,7 @@ import java.util.Set; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.InvalidCanonicalizerException; @@ -399,11 +400,7 @@ } manifestReferences = referencedManifest.getVerificationResults(); - } catch (IOException ex) { - throw new ReferenceNotInitializedException(ex); - } catch (ParserConfigurationException ex) { - throw new ReferenceNotInitializedException(ex); - } catch (SAXException ex) { + } catch (ParserConfigurationException | IOException | SAXException | XMLStreamException ex) { throw new ReferenceNotInitializedException(ex); } }
diff --git a/src/main/java/org/apache/xml/security/signature/SignedInfo.java b/src/main/java/org/apache/xml/security/signature/SignedInfo.java index 28b553d..aa46878 100644 --- a/src/main/java/org/apache/xml/security/signature/SignedInfo.java +++ b/src/main/java/org/apache/xml/security/signature/SignedInfo.java
@@ -26,6 +26,7 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.algorithms.SignatureAlgorithm; import org.apache.xml.security.c14n.CanonicalizationException; @@ -218,11 +219,7 @@ element.getParentNode().replaceChild(imported, element); return (Element) imported; } - } catch (ParserConfigurationException ex) { - throw new XMLSecurityException(ex); - } catch (IOException ex) { - throw new XMLSecurityException(ex); - } catch (SAXException ex) { + } catch (ParserConfigurationException | IOException | SAXException | XMLStreamException ex) { throw new XMLSecurityException(ex); } }
diff --git a/src/main/java/org/apache/xml/security/signature/XMLSignatureInput.java b/src/main/java/org/apache/xml/security/signature/XMLSignatureInput.java index e8f82fa..b8ba860 100644 --- a/src/main/java/org/apache/xml/security/signature/XMLSignatureInput.java +++ b/src/main/java/org/apache/xml/security/signature/XMLSignatureInput.java
@@ -30,6 +30,7 @@ import java.util.Set; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.implementations.Canonicalizer11_OmitComments; @@ -191,9 +192,10 @@ * @throws IOException * @throws ParserConfigurationException * @throws CanonicalizationException + * @throws XMLStreamException */ public Set<Node> getNodeSet() throws CanonicalizationException, ParserConfigurationException, - IOException, SAXException { + IOException, SAXException, XMLStreamException { return getNodeSet(false); } @@ -215,9 +217,10 @@ * @throws IOException * @throws ParserConfigurationException * @throws CanonicalizationException + * @throws XMLStreamException */ public Set<Node> getNodeSet(boolean circumvent) throws ParserConfigurationException, - IOException, SAXException, CanonicalizationException { + IOException, SAXException, CanonicalizationException, XMLStreamException { if (inputNodeSet != null) { return inputNodeSet; } @@ -568,7 +571,7 @@ } void convertToNodes() throws CanonicalizationException, - ParserConfigurationException, IOException, SAXException { + ParserConfigurationException, IOException, SAXException, XMLStreamException { // select all nodes, also the comments. try { Document doc = XMLUtils.read(this.getOctetStream(), secureValidation);
diff --git a/src/main/java/org/apache/xml/security/staxutils/AbstractDOMStreamReader.java b/src/main/java/org/apache/xml/security/staxutils/AbstractDOMStreamReader.java new file mode 100644 index 0000000..9484c89 --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/AbstractDOMStreamReader.java
@@ -0,0 +1,361 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import java.util.Collections; +import java.util.List; + +import javax.xml.stream.Location; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + + +/** + * Abstract logic for creating XMLStreamReader from DOM documents. Its works + * using adapters for Element, Node and Attribute. + */ +public abstract class AbstractDOMStreamReader<T, I> implements XMLStreamReader { + protected int currentEvent = XMLStreamConstants.START_DOCUMENT; + + private FastStack<ElementFrame<T, I>> frames = new FastStack<>(); + + private ElementFrame<T, I> frame; + + + /** + * + */ + public static class ElementFrame<T, I> { + T element; + I currentChild; + + boolean started; + boolean ended; + + List<String> uris; + List<String> prefixes; + List<Object> attributes; + + final ElementFrame<T, I> parent; + + public ElementFrame(T element, ElementFrame<T, I> parent) { + this.element = element; + this.parent = parent; + } + + public ElementFrame(T element, ElementFrame<T, I> parent, I ch) { + this.element = element; + this.parent = parent; + this.currentChild = ch; + } + public ElementFrame(T doc, boolean s) { + this.element = doc; + parent = null; + started = s; + attributes = Collections.emptyList(); + prefixes = Collections.emptyList(); + uris = Collections.emptyList(); + } + public ElementFrame(T doc) { + this(doc, true); + } + public T getElement() { + return element; + } + + public I getCurrentChild() { + return currentChild; + } + public void setCurrentChild(I o) { + currentChild = o; + } + public boolean isDocument() { + return false; + } + public boolean isDocumentFragment() { + return false; + } + } + + /** + * @param frame + */ + public AbstractDOMStreamReader(ElementFrame<T, I> frame) { + this.frame = frame; + frames.push(this.frame); + } + + protected ElementFrame<T, I> getCurrentFrame() { + return frame; + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#getProperty(java.lang.String) + */ + public Object getProperty(String key) throws IllegalArgumentException { + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#next() + */ + public int next() throws XMLStreamException { + if (frame.ended) { + frames.pop(); + if (!frames.empty()) { + frame = frames.peek(); + } else { + currentEvent = END_DOCUMENT; + return currentEvent; + } + } + + if (!frame.started) { + frame.started = true; + currentEvent = frame.isDocument() ? START_DOCUMENT : START_ELEMENT; + } else if (hasMoreChildren()) { + currentEvent = nextChild(); + + if (currentEvent == START_ELEMENT) { + ElementFrame<T, I> newFrame = getChildFrame(); + newFrame.started = true; + frame = newFrame; + frames.push(this.frame); + currentEvent = START_ELEMENT; + + newFrame(newFrame); + } + } else { + frame.ended = true; + if (frame.isDocument()) { + currentEvent = END_DOCUMENT; + } else { + currentEvent = END_ELEMENT; + endElement(); + } + } + return currentEvent; + } + + protected void newFrame(ElementFrame<T, I> newFrame) { + } + + protected void endElement() { + } + + protected abstract boolean hasMoreChildren(); + protected abstract int nextChild(); + protected abstract ElementFrame<T, I> getChildFrame(); + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#require(int, java.lang.String, + * java.lang.String) + */ + public void require(int arg0, String arg1, String arg2) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#getElementText() + */ + public abstract String getElementText() throws XMLStreamException; + + public void consumeFrame() { + frame.started = true; + frame.ended = true; + if (frame.isDocument()) { + currentEvent = END_DOCUMENT; + } else { + currentEvent = END_ELEMENT; + endElement(); + } + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#nextTag() + */ + public int nextTag() throws XMLStreamException { + while (hasNext()) { + if (START_ELEMENT == next()) { + return START_ELEMENT; + } + } + + return currentEvent; + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#hasNext() + */ + public boolean hasNext() throws XMLStreamException { + + return !(frame.ended && (frames.isEmpty() || frame.isDocumentFragment())); + + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#close() + */ + public void close() throws XMLStreamException { + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#getNamespaceURI(java.lang.String) + */ + public abstract String getNamespaceURI(String prefix); + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#isStartElement() + */ + public boolean isStartElement() { + return currentEvent == START_ELEMENT; + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#isEndElement() + */ + public boolean isEndElement() { + return currentEvent == END_ELEMENT; + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#isCharacters() + */ + public boolean isCharacters() { + return currentEvent == CHARACTERS; + } + + /* + * (non-Javadoc) + * + * @see javax.xml.stream.XMLStreamReader#isWhiteSpace() + */ + public boolean isWhiteSpace() { + if (currentEvent == CHARACTERS || currentEvent == CDATA) { + String text = getText(); + int len = text.length(); + for (int i = 0; i < len; ++i) { + if (text.charAt(i) > 0x0020) { + return false; + } + } + return true; + } + return currentEvent == SPACE; + } + + public int getEventType() { + return currentEvent; + } + + public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) + throws XMLStreamException { + char[] src = getText().toCharArray(); + + if (sourceStart + length >= src.length) { + length = src.length - sourceStart; + } + + for (int i = 0; i < length; i++) { + target[targetStart + i] = src[i + sourceStart]; + } + + return length; + } + + public boolean hasText() { + return currentEvent == CHARACTERS || currentEvent == DTD || currentEvent == ENTITY_REFERENCE + || currentEvent == COMMENT || currentEvent == SPACE; + } + + public String getSystemId() { + return null; + } + public String getPublicId() { + return null; + } + public Location getLocation() { + return new Location() { + + public int getCharacterOffset() { + return 0; + } + + public int getColumnNumber() { + return 0; + } + + public int getLineNumber() { + return 0; + } + + public String getPublicId() { + return AbstractDOMStreamReader.this.getPublicId(); + } + + public String getSystemId() { + return AbstractDOMStreamReader.this.getSystemId(); + } + + }; + } + + public boolean hasName() { + return currentEvent == START_ELEMENT || currentEvent == END_ELEMENT; + } + + public String getVersion() { + return null; + } + + public boolean isStandalone() { + return false; + } + + public boolean standaloneSet() { + return false; + } + + public String getCharacterEncodingScheme() { + return null; + } +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/DOMUtils.java b/src/main/java/org/apache/xml/security/staxutils/DOMUtils.java new file mode 100644 index 0000000..a833a62 --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/DOMUtils.java
@@ -0,0 +1,142 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.Text; + +/** + * Few simple utils to read DOM. This is originally from the Jakarta Commons Modeler. + */ +public final class DOMUtils { + private static final Map<ClassLoader, DocumentBuilder> DOCUMENT_BUILDERS + = Collections.synchronizedMap(new WeakHashMap<ClassLoader, DocumentBuilder>()); + private static final Map<ClassLoader, DocumentBuilder> DOCUMENT_BUILDERS_DISALLOW_DOCTYPE + = Collections.synchronizedMap(new WeakHashMap<ClassLoader, DocumentBuilder>()); + + private DOMUtils() { + } + + private static DocumentBuilder getDocumentBuilder(boolean disAllowDocTypeDeclarations) throws ParserConfigurationException { + ClassLoader loader = getContextClassLoader(); + if (loader == null) { + loader = getClassLoader(DOMUtils.class); + } + if (loader == null) { + DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); + f.setNamespaceAware(true); + f.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true); + f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", disAllowDocTypeDeclarations); + return f.newDocumentBuilder(); + } + DocumentBuilder factory = + disAllowDocTypeDeclarations ? DOCUMENT_BUILDERS_DISALLOW_DOCTYPE.get(loader) : DOCUMENT_BUILDERS.get(loader); + if (factory == null) { + DocumentBuilderFactory f2 = DocumentBuilderFactory.newInstance(); + f2.setNamespaceAware(true); + f2.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true); + f2.setFeature("http://apache.org/xml/features/disallow-doctype-decl", disAllowDocTypeDeclarations); + factory = f2.newDocumentBuilder(); + if (disAllowDocTypeDeclarations) { + DOCUMENT_BUILDERS_DISALLOW_DOCTYPE.put(loader, factory); + } else { + DOCUMENT_BUILDERS.put(loader, factory); + } + } + return factory; + } + + private static ClassLoader getContextClassLoader() { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + } + return Thread.currentThread().getContextClassLoader(); + } + + private static ClassLoader getClassLoader(final Class<?> clazz) { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + return clazz.getClassLoader(); + } + }); + } + return clazz.getClassLoader(); + } + + /** + * Creates a new Document object + * @throws ParserConfigurationException + */ + public static Document newDocument(boolean disAllowDocTypeDeclarations) { + try { + return getDocumentBuilder(disAllowDocTypeDeclarations).newDocument(); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + } + + /** + * Get the raw text content of a node or null if there is no text + */ + public static String getRawContent(Node n) { + if (n == null) { + return null; + } + StringBuilder b = null; + String s = null; + Node n1 = n.getFirstChild(); + while (n1 != null) { + if (n1.getNodeType() == Node.TEXT_NODE || n1.getNodeType() == Node.CDATA_SECTION_NODE) { + if (b != null) { + b.append(((Text)n1).getNodeValue()); + } else if (s == null) { + s = ((Text)n1).getNodeValue(); + } else { + b = new StringBuilder(s).append(((Text)n1).getNodeValue()); + s = null; + } + } + n1 = n1.getNextSibling(); + } + if (b != null) { + return b.toString(); + } + return s; + } + +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/DepthExceededStaxException.java b/src/main/java/org/apache/xml/security/staxutils/DepthExceededStaxException.java new file mode 100644 index 0000000..5886a20 --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/DepthExceededStaxException.java
@@ -0,0 +1,33 @@ +/** + * 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 org.apache.xml.security.staxutils; + +public class DepthExceededStaxException extends RuntimeException { + + private static final long serialVersionUID = 4750070687283463619L; + + public DepthExceededStaxException() { + + } + + public DepthExceededStaxException(String message) { + super(message); + } + +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/DepthRestrictingStreamReader.java b/src/main/java/org/apache/xml/security/staxutils/DepthRestrictingStreamReader.java new file mode 100644 index 0000000..6e493ae --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/DepthRestrictingStreamReader.java
@@ -0,0 +1,84 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import java.util.Stack; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +/** + * XMLStreamReader implementation which can be used to enforce a number of + * depth-restricting policies. The following properties are currently supported: + * - total number of elements in the document + * - the maximum depth of the given element; the root element will be checked by default + * - the maximum number of immediate child nodes for individual elements + * + * More sophisticated policies can be supported in the future. + */ +public class DepthRestrictingStreamReader extends DepthXMLStreamReader { + + private DocumentDepthProperties props; + private int totalElementCount; + private Stack<Integer> stack = new Stack<>(); + + public DepthRestrictingStreamReader(XMLStreamReader reader, + int elementCountThreshold, + int innerElementLevelThreshold, + int innerElementCountThreshold) { + this(reader, new DocumentDepthProperties(elementCountThreshold, + innerElementLevelThreshold, + innerElementCountThreshold)); + } + + public DepthRestrictingStreamReader(XMLStreamReader reader, + DocumentDepthProperties props) { + super(reader); + this.props = props; + } + + @Override + public int next() throws XMLStreamException { + int next = super.next(); + if (next == START_ELEMENT) { + if (props.getInnerElementLevelThreshold() != -1 + && getDepth() >= props.getInnerElementLevelThreshold()) { + throw new DepthExceededStaxException(); + } + if (props.getElementCountThreshold() != -1 + && ++totalElementCount >= props.getElementCountThreshold()) { + throw new DepthExceededStaxException(); + } + if (props.getInnerElementCountThreshold() != -1) { + if (!stack.empty()) { + int currentCount = stack.pop(); + if (++currentCount >= props.getInnerElementCountThreshold()) { + throw new DepthExceededStaxException(); + } + stack.push(currentCount); + } + stack.push(0); + } + + } else if (next == END_ELEMENT && props.getInnerElementCountThreshold() != -1) { + stack.pop(); + } + return next; + } +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/DepthXMLStreamReader.java b/src/main/java/org/apache/xml/security/staxutils/DepthXMLStreamReader.java new file mode 100644 index 0000000..3d7f14c --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/DepthXMLStreamReader.java
@@ -0,0 +1,266 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.stream.Location; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +public class DepthXMLStreamReader implements XMLStreamReader { + + protected XMLStreamReader reader; + private int depth; + + public DepthXMLStreamReader(XMLStreamReader r) { + this.reader = r; + } + + public XMLStreamReader getReader() { + return this.reader; + } + + public int getDepth() { + return depth; + } + + public void close() throws XMLStreamException { + reader.close(); + } + + public int getAttributeCount() { + return reader.getAttributeCount(); + } + + public String getAttributeLocalName(int arg0) { + return reader.getAttributeLocalName(arg0); + } + + public QName getAttributeName(int arg0) { + return reader.getAttributeName(arg0); + } + + public String getAttributeNamespace(int arg0) { + return reader.getAttributeNamespace(arg0); + } + + public String getAttributePrefix(int arg0) { + return reader.getAttributePrefix(arg0); + } + + public String getAttributeType(int arg0) { + return reader.getAttributeType(arg0); + } + + public String getAttributeValue(int arg0) { + return reader.getAttributeValue(arg0); + } + + public String getAttributeValue(String namespace, String localName) { + return reader.getAttributeValue(namespace, localName); + } + + public String getCharacterEncodingScheme() { + return reader.getCharacterEncodingScheme(); + } + + public String getElementText() throws XMLStreamException { + String ret = reader.getElementText(); + //workaround bugs in some readers that aren't properly advancing to + //the END_ELEMENT (*cough*jettison*cough*) + while (reader.getEventType() != XMLStreamConstants.END_ELEMENT) { + reader.next(); + } + depth--; + return ret; + } + + public String getEncoding() { + return reader.getEncoding(); + } + + public int getEventType() { + return reader.getEventType(); + } + + public String getLocalName() { + return reader.getLocalName(); + } + + public Location getLocation() { + return reader.getLocation(); + } + + public QName getName() { + return reader.getName(); + } + + public NamespaceContext getNamespaceContext() { + return reader.getNamespaceContext(); + } + + public int getNamespaceCount() { + return reader.getNamespaceCount(); + } + + public String getNamespacePrefix(int arg0) { + return reader.getNamespacePrefix(arg0); + } + + public String getNamespaceURI() { + return reader.getNamespaceURI(); + } + + public String getNamespaceURI(int arg0) { + + return reader.getNamespaceURI(arg0); + } + + public String getNamespaceURI(String arg0) { + return reader.getNamespaceURI(arg0); + } + + public String getPIData() { + return reader.getPIData(); + } + + public String getPITarget() { + return reader.getPITarget(); + } + + public String getPrefix() { + return reader.getPrefix(); + } + + public Object getProperty(String arg0) throws IllegalArgumentException { + + return reader.getProperty(arg0); + } + + public String getText() { + return reader.getText(); + } + + public char[] getTextCharacters() { + return reader.getTextCharacters(); + } + + public int getTextCharacters(int arg0, char[] arg1, int arg2, int arg3) throws XMLStreamException { + return reader.getTextCharacters(arg0, arg1, arg2, arg3); + } + + public int getTextLength() { + return reader.getTextLength(); + } + + public int getTextStart() { + return reader.getTextStart(); + } + + public String getVersion() { + return reader.getVersion(); + } + + public boolean hasName() { + return reader.hasName(); + } + + public boolean hasNext() throws XMLStreamException { + return reader.hasNext(); + } + + public boolean hasText() { + return reader.hasText(); + } + + public boolean isAttributeSpecified(int arg0) { + return reader.isAttributeSpecified(arg0); + } + + public boolean isCharacters() { + return reader.isCharacters(); + } + + public boolean isEndElement() { + return reader.isEndElement(); + } + + public boolean isStandalone() { + return reader.isStandalone(); + } + + public boolean isStartElement() { + return reader.isStartElement(); + } + + public boolean isWhiteSpace() { + return reader.isWhiteSpace(); + } + + public int next() throws XMLStreamException { + int next = reader.next(); + + if (next == START_ELEMENT) { + depth++; + } else if (next == END_ELEMENT) { + depth--; + } + + return next; + } + + public int nextTag() throws XMLStreamException { + int eventType = next(); + while (eventType == XMLStreamConstants.CHARACTERS && isWhiteSpace() + || eventType == XMLStreamConstants.CDATA && isWhiteSpace() + // skip whitespace + || eventType == XMLStreamConstants.SPACE + || eventType == XMLStreamConstants.PROCESSING_INSTRUCTION + || eventType == XMLStreamConstants.COMMENT) { + eventType = next(); + } + if (eventType != XMLStreamConstants.START_ELEMENT && eventType != XMLStreamConstants.END_ELEMENT) { + throw new XMLStreamException("expected start or end tag", getLocation()); + } + return eventType; + } + + public void require(int arg0, String arg1, String arg2) throws XMLStreamException { + reader.require(arg0, arg1, arg2); + } + + public boolean standaloneSet() { + return reader.standaloneSet(); + } + + public int hashCode() { + return reader.hashCode(); + } + + public boolean equals(Object arg0) { + return reader.equals(arg0); + } + + public String toString() { + return reader.toString(); + } +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/DocumentDepthProperties.java b/src/main/java/org/apache/xml/security/staxutils/DocumentDepthProperties.java new file mode 100644 index 0000000..cdd9f4b --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/DocumentDepthProperties.java
@@ -0,0 +1,64 @@ +/** + * 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 org.apache.xml.security.staxutils; + +public class DocumentDepthProperties { + + public static final String TOTAL_ELEMENT_COUNT = "depthTotalElementCountThreshold"; + public static final String INNER_ELEMENT_COUNT = "depthInnerElementCountThreshold"; + public static final String INNER_ELEMENT_LEVEL = "depthInnerElementLevelThreshold"; + + private int elementCountThreshold = -1; + private int innerElementLevelThreshold = -1; + private int innerElementCountThreshold = -1; + public DocumentDepthProperties() { + + } + public DocumentDepthProperties(int elementCountThreshold, + int innerElementLevelThreshold, + int innerElementCountThreshold) { + this.elementCountThreshold = elementCountThreshold; + this.innerElementLevelThreshold = innerElementLevelThreshold; + this.innerElementCountThreshold = innerElementCountThreshold; + } + + public boolean isEffective() { + return elementCountThreshold != -1 || innerElementLevelThreshold != -1 + || innerElementCountThreshold != -1; + } + + public void setElementCountThreshold(int elementCountThreshold) { + this.elementCountThreshold = elementCountThreshold; + } + public int getElementCountThreshold() { + return elementCountThreshold; + } + public void setInnerElementLevelThreshold(int innerElementLevelThreshold) { + this.innerElementLevelThreshold = innerElementLevelThreshold; + } + public int getInnerElementLevelThreshold() { + return innerElementLevelThreshold; + } + public void setInnerElementCountThreshold(int innerElementCountThreshold) { + this.innerElementCountThreshold = innerElementCountThreshold; + } + public int getInnerElementCountThreshold() { + return innerElementCountThreshold; + } +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/FastStack.java b/src/main/java/org/apache/xml/security/staxutils/FastStack.java new file mode 100644 index 0000000..8a48a3d --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/FastStack.java
@@ -0,0 +1,50 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import java.util.ArrayList; +import java.util.EmptyStackException; + +public class FastStack<T> extends ArrayList<T> { + private static final long serialVersionUID = -6459503295618120689L; + + public void push(T o) { + add(o); + } + + public T pop() { + if (empty()) { + throw new EmptyStackException(); + } + + return remove(size() - 1); + } + + public boolean empty() { + return size() == 0; + } + + public T peek() { + if (empty()) { + throw new EmptyStackException(); + } + + return get(size() - 1); + } +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/StaxUtils.java b/src/main/java/org/apache/xml/security/staxutils/StaxUtils.java new file mode 100644 index 0000000..756f6e9 --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/StaxUtils.java
@@ -0,0 +1,592 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Stack; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.stream.Location; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLResolver; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stax.StAXSource; +import javax.xml.transform.stream.StreamSource; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.UserDataHandler; +import org.xml.sax.InputSource; + +public final class StaxUtils { + + private static final org.slf4j.Logger LOG = + org.slf4j.LoggerFactory.getLogger(StaxUtils.class); + + private static final String XML_NS = "http://www.w3.org/2000/xmlns/"; + + private static final BlockingQueue<XMLInputFactory> DISALLOW_DOCTYPE_INPUT_FACTORY_POOL = new ArrayBlockingQueue<>(20); + private static final BlockingQueue<XMLInputFactory> ALLOW_DOCTYPE_INPUT_FACTORY_POOL = new ArrayBlockingQueue<>(20); + + private StaxUtils() { + } + + /** + * Return a cached, namespace-aware, factory. + */ + private static XMLInputFactory getXMLInputFactory(boolean disAllowDocTypeDeclarations) { + XMLInputFactory f = disAllowDocTypeDeclarations + ? DISALLOW_DOCTYPE_INPUT_FACTORY_POOL.poll() : ALLOW_DOCTYPE_INPUT_FACTORY_POOL.poll(); + if (f == null) { + f = createXMLInputFactory(disAllowDocTypeDeclarations); + } + return f; + } + + private static void returnXMLInputFactory(XMLInputFactory factory, boolean disAllowDocTypeDeclarations) { + if (disAllowDocTypeDeclarations) { + DISALLOW_DOCTYPE_INPUT_FACTORY_POOL.offer(factory); + } else { + ALLOW_DOCTYPE_INPUT_FACTORY_POOL.offer(factory); + } + } + + /** + * Return a new factory so that the caller can set sticky parameters. + * @param disAllowDocTypeDeclarations + * @throws XMLStreamException + */ + private static XMLInputFactory createXMLInputFactory(boolean disAllowDocTypeDeclarations) { + XMLInputFactory factory = null; + try { + factory = XMLInputFactory.newInstance(); + } catch (Throwable t) { + if (LOG.isDebugEnabled()) { + LOG.debug("XMLInputFactory.newInstance() failed with: ", t); + } + throw new RuntimeException("Failed to create XMLInputFactory."); + } + + setProperty(factory, XMLInputFactory.IS_NAMESPACE_AWARE, true); + + if (disAllowDocTypeDeclarations) { + setProperty(factory, XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); + setProperty(factory, XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE); + setProperty(factory, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); + factory.setXMLResolver(new XMLResolver() { + public Object resolveEntity(String publicID, String systemID, + String baseURI, String namespace) + throws XMLStreamException { + throw new XMLStreamException("Reading external entities is disabled"); + } + }); + } + + return factory; + } + + private static boolean setProperty(XMLInputFactory f, String p, Object o) { + try { + f.setProperty(p, o); + return true; + } catch (Throwable t) { + //ignore + } + return false; + } + + public static Document read(Source s, boolean disAllowDocTypeDeclarations) throws XMLStreamException { + XMLStreamReader reader = createXMLStreamReader(s, disAllowDocTypeDeclarations); + try { + return read(reader); + } finally { + try { + reader.close(); + } catch (Exception ex) { + //ignore + } + } + } + public static Document read(InputStream s, boolean disAllowDocTypeDeclarations) throws XMLStreamException { + XMLStreamReader reader = createXMLStreamReader(s, disAllowDocTypeDeclarations); + try { + return read(reader); + } finally { + try { + reader.close(); + } catch (Exception ex) { + //ignore + } + } + } + public static Document read(Reader s, boolean disAllowDocTypeDeclarations) throws XMLStreamException { + XMLStreamReader reader = createXMLStreamReader(s, disAllowDocTypeDeclarations); + try { + return read(reader); + } finally { + try { + reader.close(); + } catch (Exception ex) { + //ignore + } + } + } + public static Document read(File is, boolean disAllowDocTypeDeclarations) throws XMLStreamException, IOException { + try (InputStream fin = Files.newInputStream(is.toPath())) { + return read(fin, disAllowDocTypeDeclarations); + } + } + public static Document read(InputSource s, boolean disAllowDocTypeDeclarations) throws XMLStreamException { + XMLStreamReader reader = createXMLStreamReader(s, disAllowDocTypeDeclarations); + try { + return read(reader); + } finally { + try { + reader.close(); + } catch (Exception ex) { + //ignore + } + } + } + public static Document read(XMLStreamReader reader) throws XMLStreamException { + return read(reader, false); + } + public static Document read(XMLStreamReader reader, boolean recordLoc) throws XMLStreamException { + Document doc = DOMUtils.newDocument(true); + if (reader.getLocation().getSystemId() != null) { + try { + doc.setDocumentURI(reader.getLocation().getSystemId()); + } catch (Exception e) { + //ignore - probably not DOM level 3 + } + } + readDocElements(doc, doc, reader, true, recordLoc); + return doc; + } + + public static Document read(DocumentBuilder builder, XMLStreamReader reader, boolean repairing) + throws XMLStreamException { + + Document doc = builder == null ? DOMUtils.newDocument(true) : builder.newDocument(); + if (reader.getLocation().getSystemId() != null) { + try { + doc.setDocumentURI(reader.getLocation().getSystemId()); + } catch (Exception e) { + //ignore - probably not DOM level 3 + } + } + readDocElements(doc, reader, repairing); + return doc; + } + + /** + * @param parent + */ + private static Document getDocument(Node parent) { + return parent instanceof Document ? (Document)parent : parent.getOwnerDocument(); + } + + private static boolean isDeclared(Element e, String namespaceURI, String prefix) { + while (e != null) { + Attr att; + if (prefix != null && prefix.length() > 0) { + att = e.getAttributeNodeNS(XML_NS, prefix); + } else { + att = e.getAttributeNode("xmlns"); + } + + if (att != null && att.getNodeValue().equals(namespaceURI)) { + return true; + } + + if (e.getParentNode() instanceof Element) { + e = (Element)e.getParentNode(); + } else if (isEmpty(prefix) && isEmpty(namespaceURI)) { + //A document that probably doesn't have any namespace qualifies elements + return true; + } else { + e = null; + } + } + return false; + } + + public static void readDocElements(Node parent, XMLStreamReader reader, boolean repairing) + throws XMLStreamException { + Document doc = getDocument(parent); + readDocElements(doc, parent, reader, repairing, false); + } + + public static void readDocElements(Node parent, XMLStreamReader reader, boolean repairing, + boolean isThreshold) + throws XMLStreamException { + Document doc = getDocument(parent); + readDocElements(doc, parent, reader, repairing, false, isThreshold); + } + + /** + * @param parent + * @param reader + * @throws XMLStreamException + */ + public static void readDocElements(Document doc, Node parent, + XMLStreamReader reader, boolean repairing, boolean recordLoc) + throws XMLStreamException { + readDocElements(doc, parent, reader, repairing, recordLoc, false); + } + + /** + * @param parent + * @param reader + * @throws XMLStreamException + */ + public static void readDocElements(Document doc, Node parent, + XMLStreamReader reader, boolean repairing, boolean recordLoc, + boolean isThreshold) + throws XMLStreamException { + Stack<Node> stack = new Stack<Node>(); + int event = reader.getEventType(); + while (reader.hasNext()) { + switch (event) { + case XMLStreamConstants.START_ELEMENT: { + Element e; + if (!isEmpty(reader.getPrefix())) { + e = doc.createElementNS(reader.getNamespaceURI(), + reader.getPrefix() + ":" + reader.getLocalName()); + } else { + e = doc.createElementNS(reader.getNamespaceURI(), reader.getLocalName()); + } + e = (Element)parent.appendChild(e); + recordLoc = addLocation(doc, e, reader, recordLoc); + + for (int ns = 0; ns < reader.getNamespaceCount(); ns++) { + String uri = reader.getNamespaceURI(ns); + String prefix = reader.getNamespacePrefix(ns); + + declare(e, uri, prefix); + } + + for (int att = 0; att < reader.getAttributeCount(); att++) { + String name = reader.getAttributeLocalName(att); + String prefix = reader.getAttributePrefix(att); + if (prefix != null && prefix.length() > 0) { + name = prefix + ":" + name; + } + + Attr attr = doc.createAttributeNS(reader.getAttributeNamespace(att), name); + attr.setValue(reader.getAttributeValue(att)); + e.setAttributeNode(attr); + } + + if (repairing && !isDeclared(e, reader.getNamespaceURI(), reader.getPrefix())) { + declare(e, reader.getNamespaceURI(), reader.getPrefix()); + } + stack.push(parent); + parent = e; + break; + } + case XMLStreamConstants.END_ELEMENT: + if (stack.isEmpty()) { + return; + } + parent = stack.pop(); + //if (parent instanceof Document || parent instanceof DocumentFragment) { + // return; + //} + break; + case XMLStreamConstants.NAMESPACE: + break; + case XMLStreamConstants.ATTRIBUTE: + break; + case XMLStreamConstants.CHARACTERS: + if (parent != null) { + recordLoc = addLocation(doc, + parent.appendChild(doc.createTextNode(reader.getText())), + reader, recordLoc); + } + break; + case XMLStreamConstants.COMMENT: + if (parent != null) { + parent.appendChild(doc.createComment(reader.getText())); + } + break; + case XMLStreamConstants.CDATA: + recordLoc = addLocation(doc, + parent.appendChild(doc.createCDATASection(reader.getText())), + reader, recordLoc); + break; + case XMLStreamConstants.PROCESSING_INSTRUCTION: + parent.appendChild(doc.createProcessingInstruction(reader.getPITarget(), reader.getPIData())); + break; + case XMLStreamConstants.ENTITY_REFERENCE: + parent.appendChild(doc.createProcessingInstruction(reader.getPITarget(), reader.getPIData())); + break; + default: + break; + } + + if (reader.hasNext()) { + event = reader.next(); + } + } + } + + private static boolean addLocation(Document doc, Node node, + Location loc, + boolean recordLoc) { + if (recordLoc && loc != null && (loc.getColumnNumber() != 0 || loc.getLineNumber() != 0)) { + try { + final int charOffset = loc.getCharacterOffset(); + final int colNum = loc.getColumnNumber(); + final int linNum = loc.getLineNumber(); + final String pubId = loc.getPublicId() == null ? doc.getDocumentURI() : loc.getPublicId(); + final String sysId = loc.getSystemId() == null ? doc.getDocumentURI() : loc.getSystemId(); + Location loc2 = new Location() { + public int getCharacterOffset() { + return charOffset; + } + public int getColumnNumber() { + return colNum; + } + public int getLineNumber() { + return linNum; + } + public String getPublicId() { + return pubId; + } + public String getSystemId() { + return sysId; + } + }; + node.setUserData("location", loc2, LocationUserDataHandler.INSTANCE); + } catch (Throwable ex) { + //possibly not DOM level 3, won't be able to record this then + return false; + } + } + return recordLoc; + } + + private static boolean addLocation(Document doc, Node node, + XMLStreamReader reader, + boolean recordLoc) { + return addLocation(doc, node, reader.getLocation(), recordLoc); + } + + private static class LocationUserDataHandler implements UserDataHandler { + public static final LocationUserDataHandler INSTANCE = new LocationUserDataHandler(); + + public void handle(short operation, String key, Object data, Node src, Node dst) { + if (operation == NODE_CLONED) { + dst.setUserData(key, data, this); + } + } + } + + private static void declare(Element node, String uri, String prefix) { + String qualname; + if (prefix != null && prefix.length() > 0) { + qualname = "xmlns:" + prefix; + } else { + qualname = "xmlns"; + } + Attr attr = node.getOwnerDocument().createAttributeNS(XML_NS, qualname); + attr.setValue(uri); + node.setAttributeNodeNS(attr); + } + + public static XMLStreamReader createXMLStreamReader(InputSource src, boolean disAllowDocTypeDeclarations) { + String sysId = src.getSystemId() == null ? null : src.getSystemId(); + String pubId = src.getPublicId() == null ? null : src.getPublicId(); + if (src.getByteStream() != null) { + if (src.getEncoding() == null) { + StreamSource ss = new StreamSource(src.getByteStream(), sysId); + ss.setPublicId(pubId); + return createXMLStreamReader(ss, disAllowDocTypeDeclarations); + } + return createXMLStreamReader(src.getByteStream(), src.getEncoding(), disAllowDocTypeDeclarations); + } else if (src.getCharacterStream() != null) { + StreamSource ss = new StreamSource(src.getCharacterStream(), sysId); + ss.setPublicId(pubId); + return createXMLStreamReader(ss, disAllowDocTypeDeclarations); + } else { + try { + URL url = new URL(sysId); + StreamSource ss = new StreamSource(url.openStream(), sysId); + ss.setPublicId(pubId); + return createXMLStreamReader(ss, disAllowDocTypeDeclarations); + } catch (Exception ex) { + //ignore - not a valid URL + } + } + throw new IllegalArgumentException("InputSource must have a ByteStream or CharacterStream"); + } + /** + * @param in + * @param encoding + */ + public static XMLStreamReader createXMLStreamReader(InputStream in, String encoding, boolean disAllowDocTypeDeclarations) { + if (encoding == null) { + encoding = StandardCharsets.UTF_8.name(); + } + + XMLInputFactory factory = getXMLInputFactory(disAllowDocTypeDeclarations); + try { + return factory.createXMLStreamReader(in, encoding); + } catch (XMLStreamException e) { + throw new RuntimeException("Couldn't parse stream.", e); + } finally { + returnXMLInputFactory(factory, disAllowDocTypeDeclarations); + } + } + + /** + * @param in + */ + public static XMLStreamReader createXMLStreamReader(InputStream in, boolean disAllowDocTypeDeclarations) { + XMLInputFactory factory = getXMLInputFactory(disAllowDocTypeDeclarations); + try { + return factory.createXMLStreamReader(in); + } catch (XMLStreamException e) { + throw new RuntimeException("Couldn't parse stream.", e); + } finally { + returnXMLInputFactory(factory, disAllowDocTypeDeclarations); + } + } + public static XMLStreamReader createXMLStreamReader(String systemId, InputStream in, boolean disAllowDocTypeDeclarations) { + XMLInputFactory factory = getXMLInputFactory(disAllowDocTypeDeclarations); + try { + return factory.createXMLStreamReader(systemId, in); + } catch (XMLStreamException e) { + throw new RuntimeException("Couldn't parse stream.", e); + } finally { + returnXMLInputFactory(factory, disAllowDocTypeDeclarations); + } + } + + public static XMLStreamReader createXMLStreamReader(Element el) { + return new W3CDOMStreamReader(el); + } + public static XMLStreamReader createXMLStreamReader(Document doc) { + return new W3CDOMStreamReader(doc.getDocumentElement()); + } + public static XMLStreamReader createXMLStreamReader(Element el, String sysId) { + return new W3CDOMStreamReader(el, sysId); + } + public static XMLStreamReader createXMLStreamReader(Document doc, String sysId) { + return new W3CDOMStreamReader(doc.getDocumentElement(), sysId); + } + + public static XMLStreamReader createXMLStreamReader(Source source, boolean disAllowDocTypeDeclarations) { + try { + if (source instanceof DOMSource) { + DOMSource ds = (DOMSource)source; + Node nd = ds.getNode(); + Element el = null; + if (nd instanceof Document) { + el = ((Document)nd).getDocumentElement(); + } else if (nd instanceof Element) { + el = (Element)nd; + } + + if (null != el) { + return new W3CDOMStreamReader(el, source.getSystemId()); + } + } else if (source instanceof StAXSource) { + return ((StAXSource)source).getXMLStreamReader(); + } else if (source instanceof SAXSource) { + SAXSource ss = (SAXSource)source; + if (ss.getXMLReader() == null) { + return createXMLStreamReader(((SAXSource)source).getInputSource(), disAllowDocTypeDeclarations); + } + } + + XMLInputFactory factory = getXMLInputFactory(disAllowDocTypeDeclarations); + try { + XMLStreamReader reader = null; + + try { + reader = factory.createXMLStreamReader(source); + } catch (UnsupportedOperationException e) { + //ignore + } + if (reader == null && source instanceof StreamSource) { + //createXMLStreamReader from Source is optional, we'll try and map it + StreamSource ss = (StreamSource)source; + if (ss.getInputStream() != null) { + reader = factory.createXMLStreamReader(ss.getSystemId(), + ss.getInputStream()); + } else { + reader = factory.createXMLStreamReader(ss.getSystemId(), + ss.getReader()); + } + } + return reader; + } finally { + returnXMLInputFactory(factory, disAllowDocTypeDeclarations); + } + } catch (XMLStreamException e) { + throw new RuntimeException("Couldn't parse stream.", e); + } + } + + /** + * @param reader + */ + public static XMLStreamReader createXMLStreamReader(Reader reader, boolean disAllowDocTypeDeclarations) { + XMLInputFactory factory = getXMLInputFactory(disAllowDocTypeDeclarations); + try { + return factory.createXMLStreamReader(reader); + } catch (XMLStreamException e) { + throw new RuntimeException("Couldn't parse stream.", e); + } finally { + returnXMLInputFactory(factory, disAllowDocTypeDeclarations); + } + } + + private static boolean isEmpty(String str) { + if (str != null) { + int len = str.length(); + for (int x = 0; x < len; ++x) { + if (str.charAt(x) > ' ') { + return false; + } + } + } + return true; + } + + +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/SystemPropertyAction.java b/src/main/java/org/apache/xml/security/staxutils/SystemPropertyAction.java new file mode 100644 index 0000000..5757e60 --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/SystemPropertyAction.java
@@ -0,0 +1,97 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * + */ +public final class SystemPropertyAction implements PrivilegedAction<String> { + private static final org.slf4j.Logger LOG = + org.slf4j.LoggerFactory.getLogger(SystemPropertyAction.class); + private final String property; + private final String def; + private SystemPropertyAction(String name) { + property = name; + def = null; + } + private SystemPropertyAction(String name, String d) { + property = name; + def = d; + } + + /* (non-Javadoc) + * @see java.security.PrivilegedAction#run() + */ + public String run() { + if (def != null) { + return System.getProperty(property, def); + } + return System.getProperty(property); + } + + public static String getProperty(String name) { + return AccessController.doPrivileged(new SystemPropertyAction(name)); + } + + public static String getProperty(String name, String def) { + try { + return AccessController.doPrivileged(new SystemPropertyAction(name, def)); + } catch (SecurityException ex) { + LOG.debug("SecurityException raised getting property " + name, ex); + return def; + } + } + + /** + * Get the system property via the AccessController, but if a SecurityException is + * raised, just return null; + * @param name + */ + public static String getPropertyOrNull(String name) { + try { + return AccessController.doPrivileged(new SystemPropertyAction(name)); + } catch (SecurityException ex) { + LOG.debug("SecurityException raised getting property " + name, ex); + return null; + } + } + + /** + * Get the integer system property via the AccessController, but if a SecurityException is + * raised, just return the default; + * @param name - system property name + * @param def - the default value if the system property does not exist or cannot be acquired + */ + public static int getInteger(String name, int def) { + try { + return AccessController.doPrivileged(new PrivilegedAction<Integer>() { + @Override + public Integer run() { + return Integer.getInteger(name, def); + } }); + } catch (SecurityException ex) { + LOG.debug("SecurityException raised getting property " + name, ex); + return def; + } + } +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/W3CDOMStreamReader.java b/src/main/java/org/apache/xml/security/staxutils/W3CDOMStreamReader.java new file mode 100644 index 0000000..9596d74 --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/W3CDOMStreamReader.java
@@ -0,0 +1,419 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import java.util.ArrayList; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.stream.Location; +import javax.xml.stream.XMLStreamException; + +import org.w3c.dom.Attr; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; +import org.w3c.dom.TypeInfo; + +public class W3CDOMStreamReader extends AbstractDOMStreamReader<Node, Node> { + private Node content; + + private Document document; + + private W3CNamespaceContext context; + + private String sysId; + + /** + * @param element + */ + public W3CDOMStreamReader(Element element) { + super(new ElementFrame<Node, Node>(element, null)); + content = element; + newFrame(getCurrentFrame()); + + this.document = element.getOwnerDocument(); + } + public W3CDOMStreamReader(Element element, String systemId) { + this(element); + sysId = systemId; + } + public W3CDOMStreamReader(Document doc) { + super(new ElementFrame<Node, Node>(doc, false) { + public boolean isDocument() { + return true; + } + }); + this.document = doc; + } + public W3CDOMStreamReader(DocumentFragment docfrag) { + super(new ElementFrame<Node, Node>(docfrag, true) { + public boolean isDocumentFragment() { + return true; + } + }); + this.document = docfrag.getOwnerDocument(); + } + + /** + * Get the document associated with this stream. + */ + public Document getDocument() { + return document; + } + public String getSystemId() { + try { + return sysId == null ? document.getDocumentURI() : sysId; + } catch (Throwable ex) { + //ignore, probably not DOM level 3 + } + return sysId; + } + /** + * Find name spaces declaration in atrributes and move them to separate + * collection. + */ + @Override + protected final void newFrame(ElementFrame<Node, Node> frame) { + Node element = getCurrentNode(); + frame.uris = new ArrayList<>(); + frame.prefixes = new ArrayList<>(); + frame.attributes = new ArrayList<>(); + + if (context == null) { + context = new W3CNamespaceContext(); + } + if (element instanceof Element) { + context.setElement((Element)element); + } + + NamedNodeMap nodes = element.getAttributes(); + + String ePrefix = element.getPrefix(); + if (ePrefix == null) { + ePrefix = ""; + } + + if (nodes != null) { + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + String prefix = node.getPrefix(); + String localName = node.getLocalName(); + String value = node.getNodeValue(); + String name = node.getNodeName(); + + if (prefix == null) { + prefix = ""; + } + + if (name != null && "xmlns".equals(name)) { + frame.uris.add(value); + frame.prefixes.add(""); + } else if (prefix.length() > 0 && "xmlns".equals(prefix)) { + frame.uris.add(value); + frame.prefixes.add(localName); + } else if (name.startsWith("xmlns:")) { + prefix = name.substring(6); + frame.uris.add(value); + frame.prefixes.add(prefix); + } else { + frame.attributes.add(node); + } + } + } + } + + public final Node getCurrentNode() { + return getCurrentFrame().element; + } + public final Element getCurrentElement() { + return (Element)getCurrentFrame().element; + } + + @Override + protected ElementFrame<Node, Node> getChildFrame() { + return new ElementFrame<Node, Node>(getCurrentFrame().currentChild, + getCurrentFrame()); + } + + @Override + protected boolean hasMoreChildren() { + if (getCurrentFrame().currentChild == null) { + return getCurrentNode().getFirstChild() != null; + } + return getCurrentFrame().currentChild.getNextSibling() != null; + } + + @Override + protected int nextChild() { + ElementFrame<Node, Node> frame = getCurrentFrame(); + if (frame.currentChild == null) { + content = getCurrentNode().getFirstChild(); + } else { + content = frame.currentChild.getNextSibling(); + } + + frame.currentChild = content; + switch (content.getNodeType()) { + case Node.ELEMENT_NODE: + return START_ELEMENT; + case Node.TEXT_NODE: + return CHARACTERS; + case Node.COMMENT_NODE: + return COMMENT; + case Node.CDATA_SECTION_NODE: + return CDATA; + case Node.ENTITY_REFERENCE_NODE: + return ENTITY_REFERENCE; + case Node.PROCESSING_INSTRUCTION_NODE: + return PROCESSING_INSTRUCTION; + default: + throw new IllegalStateException("Found type: " + content.getClass().getName()); + } + } + + @Override + public String getElementText() throws XMLStreamException { + String result = DOMUtils.getRawContent(content); + + ElementFrame<Node, Node> frame = getCurrentFrame(); + frame.ended = true; + currentEvent = END_ELEMENT; + endElement(); + + // we should not return null according to the StAx API javadoc + return result != null ? result : ""; + } + + @Override + public String getNamespaceURI(String prefix) { + ElementFrame<Node, Node> frame = getCurrentFrame(); + + while (null != frame) { + int index = frame.prefixes.indexOf(prefix); + if (index != -1) { + return frame.uris.get(index); + } + + if (frame.parent == null && frame.getElement() instanceof Element) { + return ((Element)frame.getElement()).lookupNamespaceURI(prefix); + } + frame = frame.parent; + } + + return null; + } + + public String getAttributeValue(String ns, String local) { + Attr at; + if (ns == null || ns.equals("")) { + at = getCurrentElement().getAttributeNode(local); + } else { + at = getCurrentElement().getAttributeNodeNS(ns, local); + } + + if (at == null) { + return null; + } + return at.getNodeValue(); + } + + public int getAttributeCount() { + return getCurrentFrame().attributes.size(); + } + + Attr getAttribute(int i) { + return (Attr)getCurrentFrame().attributes.get(i); + } + + private String getLocalName(Attr attr) { + + String name = attr.getLocalName(); + if (name == null) { + name = attr.getNodeName(); + } + return name; + } + + public QName getAttributeName(int i) { + Attr at = getAttribute(i); + + String prefix = at.getPrefix(); + String ln = getLocalName(at); + // at.getNodeName(); + String ns = at.getNamespaceURI(); + + if (prefix == null) { + return new QName(ns, ln); + } + return new QName(ns, ln, prefix); + } + + public String getAttributeNamespace(int i) { + return getAttribute(i).getNamespaceURI(); + } + + public String getAttributeLocalName(int i) { + Attr attr = getAttribute(i); + return getLocalName(attr); + } + + public String getAttributePrefix(int i) { + return getAttribute(i).getPrefix(); + } + + public String getAttributeType(int i) { + Attr attr = getAttribute(i); + if (attr.isId()) { + return "ID"; + } + TypeInfo schemaType = null; + try { + schemaType = attr.getSchemaTypeInfo(); + } catch (Throwable t) { + //DOM level 2? + schemaType = null; + } + return (schemaType == null) ? "CDATA" + : schemaType.getTypeName() == null ? "CDATA" : schemaType.getTypeName(); + } + + + public String getAttributeValue(int i) { + return getAttribute(i).getValue(); + } + + public boolean isAttributeSpecified(int i) { + return getAttribute(i).getValue() != null; + } + + public int getNamespaceCount() { + return getCurrentFrame().prefixes.size(); + } + + public String getNamespacePrefix(int i) { + return getCurrentFrame().prefixes.get(i); + } + + public String getNamespaceURI(int i) { + return getCurrentFrame().uris.get(i); + } + + public NamespaceContext getNamespaceContext() { + return context; + } + + public String getText() { + if (content instanceof Text) { + return ((Text)content).getData(); + } else if (content instanceof Comment) { + return ((Comment)content).getData(); + } + return DOMUtils.getRawContent(getCurrentNode()); + } + + public char[] getTextCharacters() { + return getText().toCharArray(); + } + + public int getTextStart() { + return 0; + } + + public int getTextLength() { + return getText().length(); + } + + public String getEncoding() { + return null; + } + + public QName getName() { + Node el = getCurrentNode(); + + String prefix = getPrefix(); + String ln = getLocalName(); + + return new QName(el.getNamespaceURI(), ln, prefix); + } + + public String getLocalName() { + String ln = getCurrentNode().getLocalName(); + if (ln == null) { + ln = getCurrentNode().getNodeName(); + if (ln.indexOf(":") != -1) { + ln = ln.substring(ln.indexOf(":") + 1); + } + } + return ln; + } + + public String getNamespaceURI() { + String ln = getCurrentNode().getLocalName(); + if (ln == null) { + ln = getCurrentNode().getNodeName(); + if (ln.indexOf(":") == -1) { + ln = getNamespaceURI(""); + } else { + ln = getNamespaceURI(ln.substring(0, ln.indexOf(":"))); + } + return ln; + } + return getCurrentNode().getNamespaceURI(); + } + + public String getPrefix() { + String prefix = getCurrentNode().getPrefix(); + if (prefix == null) { + String nodeName = getCurrentNode().getNodeName(); + if (nodeName.indexOf(":") != -1) { + prefix = nodeName.substring(0, nodeName.indexOf(":")); + } else { + prefix = ""; + } + } + return prefix; + } + + public String getPITarget() { + return ((ProcessingInstruction)content).getTarget(); + } + + public String getPIData() { + return ((ProcessingInstruction)content).getData(); + } + public Location getLocation() { + try { + Object o = getCurrentNode().getUserData("location"); + if (o instanceof Location) { + return (Location)o; + } + } catch (Throwable ex) { + //ignore, probably not DOM level 3 + } + return super.getLocation(); + } + + +}
diff --git a/src/main/java/org/apache/xml/security/staxutils/W3CNamespaceContext.java b/src/main/java/org/apache/xml/security/staxutils/W3CNamespaceContext.java new file mode 100644 index 0000000..647e6dc --- /dev/null +++ b/src/main/java/org/apache/xml/security/staxutils/W3CNamespaceContext.java
@@ -0,0 +1,141 @@ +/** + * 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 org.apache.xml.security.staxutils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.xml.namespace.NamespaceContext; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +public class W3CNamespaceContext implements NamespaceContext { + private Element currentNode; + private NamespaceContext outNamespaceContext; + + public W3CNamespaceContext() { + } + public W3CNamespaceContext(Element el) { + currentNode = el; + } + + public void setOutNamespaceContext(NamespaceContext context) { + outNamespaceContext = context; + } + + public String getNamespaceURI(String prefix) { + String name = prefix; + if (name.length() == 0) { + name = "xmlns"; + } else { + name = "xmlns:" + prefix; + } + + return getNamespaceURI(currentNode, name); + } + + private String getNamespaceURI(Element e, String name) { + if (e == null) { + return null; + } + // check the outside namespace URI + if (outNamespaceContext != null) { + String result = outNamespaceContext.getNamespaceURI(name); + if (result != null) { + return result; + } + } + + Attr attr = e.getAttributeNode(name); + if (attr == null) { + Node n = e.getParentNode(); + if (n instanceof Element && n != e) { + return getNamespaceURI((Element)n, name); + } + } else { + return attr.getValue(); + } + + return null; + } + + public String getPrefix(String uri) { + return getPrefix(currentNode, uri); + } + + private String getPrefix(Element e, String uri) { + if (e == null) { + return null; + } + // check the outside namespace URI + if (outNamespaceContext != null) { + String result = outNamespaceContext.getPrefix(uri); + if (result != null) { + return result; + } + } + + NamedNodeMap attributes = e.getAttributes(); + if (attributes != null) { + for (int i = 0; i < attributes.getLength(); i++) { + Attr a = (Attr)attributes.item(i); + + String val = a.getValue(); + if (val != null && val.equals(uri)) { + String name = a.getLocalName(); + if ("xmlns".equals(name)) { + return ""; + } + return name; + } + } + } + + Node n = e.getParentNode(); + if (n instanceof Element && n != e) { + return getPrefix((Element)n, uri); + } + + return null; + } + + public Iterator<String> getPrefixes(String uri) { + List<String> prefixes = new ArrayList<>(); + + String prefix = getPrefix(uri); + if (prefix != null) { + prefixes.add(prefix); + } + + return prefixes.iterator(); + } + + public Element getElement() { + return currentNode; + } + + public void setElement(Element node) { + this.currentNode = node; + } +}
diff --git a/src/main/java/org/apache/xml/security/transforms/implementations/TransformBase64Decode.java b/src/main/java/org/apache/xml/security/transforms/implementations/TransformBase64Decode.java index 0c1b3c6..e94e115 100644 --- a/src/main/java/org/apache/xml/security/transforms/implementations/TransformBase64Decode.java +++ b/src/main/java/org/apache/xml/security/transforms/implementations/TransformBase64Decode.java
@@ -22,6 +22,7 @@ import java.io.OutputStream; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.signature.XMLSignatureInput; @@ -156,6 +157,8 @@ throw new TransformationException(e, "c14n.Canonicalizer.Exception"); } catch (SAXException e) { throw new TransformationException(e, "SAX exception"); + } catch (XMLStreamException e) { + throw new TransformationException(e, "SAX exception"); } }
diff --git a/src/main/java/org/apache/xml/security/transforms/implementations/TransformXPath2Filter.java b/src/main/java/org/apache/xml/security/transforms/implementations/TransformXPath2Filter.java index 9844670..3726a8f 100644 --- a/src/main/java/org/apache/xml/security/transforms/implementations/TransformXPath2Filter.java +++ b/src/main/java/org/apache/xml/security/transforms/implementations/TransformXPath2Filter.java
@@ -26,6 +26,7 @@ import java.util.Set; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; import javax.xml.transform.TransformerException; import org.apache.xml.security.c14n.CanonicalizationException; @@ -144,11 +145,7 @@ throw new TransformationException(ex); } catch (XMLSecurityException ex) { throw new TransformationException(ex); - } catch (SAXException ex) { - throw new TransformationException(ex); - } catch (IOException ex) { - throw new TransformationException(ex); - } catch (ParserConfigurationException ex) { + } catch (ParserConfigurationException | IOException | SAXException | XMLStreamException ex) { throw new TransformationException(ex); } }
diff --git a/src/main/java/org/apache/xml/security/utils/XMLUtils.java b/src/main/java/org/apache/xml/security/utils/XMLUtils.java index 2cc91f4..5f964a1 100644 --- a/src/main/java/org/apache/xml/security/utils/XMLUtils.java +++ b/src/main/java/org/apache/xml/security/utils/XMLUtils.java
@@ -31,14 +31,15 @@ import java.util.List; import java.util.Set; -import javax.xml.XMLConstants; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.c14n.InvalidCanonicalizerException; +import org.apache.xml.security.staxutils.DOMUtils; +import org.apache.xml.security.staxutils.StaxUtils; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -59,13 +60,6 @@ AccessController.doPrivileged( (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("org.apache.xml.security.ignoreLineBreaks")); - @SuppressWarnings("unchecked") - private static final WeakObjectPool<DocumentBuilder, ParserConfigurationException> pools[] = new WeakObjectPool[2]; - static { - pools[0] = new DocumentBuilderPool(false); - pools[1] = new DocumentBuilderPool(true); - } - private static volatile String dsPrefix = "ds"; private static volatile String ds11Prefix = "dsig11"; private static volatile String xencPrefix = "xenc"; @@ -1004,55 +998,61 @@ } public static Document newDocument() throws ParserConfigurationException { - DocumentBuilder documentBuilder = createDocumentBuilder(true); - Document doc = documentBuilder.newDocument(); - repoolDocumentBuilder(documentBuilder, true); - return doc; + return DOMUtils.newDocument(true); } - public static Document read(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException { + public static Document read(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException, XMLStreamException { return read(inputStream, true); } - public static Document read(InputStream inputStream, boolean disAllowDocTypeDeclarations) throws ParserConfigurationException, SAXException, IOException { - DocumentBuilder documentBuilder = createDocumentBuilder(disAllowDocTypeDeclarations); - Document doc = documentBuilder.parse(inputStream); - repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations); - return doc; + public static Document read(InputStream inputStream, boolean disAllowDocTypeDeclarations) + throws ParserConfigurationException, SAXException, IOException, XMLStreamException { + XMLStreamReader reader = StaxUtils.createXMLStreamReader(inputStream, disAllowDocTypeDeclarations); + try { + Document doc = DOMUtils.newDocument(disAllowDocTypeDeclarations); + if (reader.getLocation().getSystemId() != null) { + try { + doc.setDocumentURI(reader.getLocation().getSystemId()); + } catch (Exception e) { + //ignore - probably not DOM level 3 + } + } + StaxUtils.readDocElements(doc, doc, reader, true, false); + return doc; + } finally { + try { + reader.close(); + } catch (Exception ex) { + //ignore + } + } } - public static Document read(String uri, boolean disAllowDocTypeDeclarations) - throws ParserConfigurationException, SAXException, IOException { - DocumentBuilder documentBuilder = createDocumentBuilder(disAllowDocTypeDeclarations); - Document doc = documentBuilder.parse(uri); - repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations); - return doc; - } - - public static Document read(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException { + public static Document read(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException, XMLStreamException { return read(inputSource, true); } public static Document read(InputSource inputSource, boolean disAllowDocTypeDeclarations) - throws ParserConfigurationException, SAXException, IOException { - DocumentBuilder documentBuilder = createDocumentBuilder(disAllowDocTypeDeclarations); - Document doc = documentBuilder.parse(inputSource); - repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations); - return doc; - } - - private static DocumentBuilder createDocumentBuilder( - boolean disAllowDocTypeDeclarations - ) throws ParserConfigurationException { - int idx = getPoolsIndex(disAllowDocTypeDeclarations); - return pools[idx].getObject(); - } - - - private static boolean repoolDocumentBuilder(DocumentBuilder db, boolean disAllowDocTypeDeclarations) { - db.reset(); - int idx = getPoolsIndex(disAllowDocTypeDeclarations); - return pools[idx].repool(db); + throws ParserConfigurationException, SAXException, IOException, XMLStreamException { + XMLStreamReader reader = StaxUtils.createXMLStreamReader(inputSource, disAllowDocTypeDeclarations); + try { + Document doc = DOMUtils.newDocument(disAllowDocTypeDeclarations); + if (reader.getLocation().getSystemId() != null) { + try { + doc.setDocumentURI(reader.getLocation().getSystemId()); + } catch (Exception e) { + //ignore - probably not DOM level 3 + } + } + StaxUtils.readDocElements(doc, doc, reader, true, false); + return doc; + } finally { + try { + reader.close(); + } catch (Exception ex) { + //ignore + } + } } /** @@ -1100,34 +1100,4 @@ return resizedBytes; } - private static final class DocumentBuilderPool - extends WeakObjectPool<DocumentBuilder, ParserConfigurationException> { - - private final boolean disAllowDocTypeDeclarations; - - public DocumentBuilderPool(boolean disAllowDocTypeDeclarations) { - this.disAllowDocTypeDeclarations = disAllowDocTypeDeclarations; - } - - @Override - protected DocumentBuilder createObject() throws ParserConfigurationException { - DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); - dfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); - if (disAllowDocTypeDeclarations) { - dfactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - } - dfactory.setNamespaceAware(true); - return dfactory.newDocumentBuilder(); - } - } - - /** - * Maps the boolean configuration options for the factories to the array index for the WeakObjectPool - * @param disAllowDocTypeDeclarations - * @return the index to the {@link #pools} - */ - private static int getPoolsIndex(boolean disAllowDocTypeDeclarations) { - return (disAllowDocTypeDeclarations ? 1 : 0); - } - }
diff --git a/src/test/java/org/apache/xml/security/test/dom/TestUtils.java b/src/test/java/org/apache/xml/security/test/dom/TestUtils.java index 8bdacc2..53f9484 100644 --- a/src/test/java/org/apache/xml/security/test/dom/TestUtils.java +++ b/src/test/java/org/apache/xml/security/test/dom/TestUtils.java
@@ -18,9 +18,19 @@ */ package org.apache.xml.security.test.dom; +import java.io.FileInputStream; +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.xml.security.staxutils.DOMUtils; +import org.apache.xml.security.staxutils.StaxUtils; import org.apache.xml.security.utils.Constants; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.xml.sax.SAXException; public class TestUtils { @@ -42,4 +52,27 @@ return ctx; } + public static Document read(String uri, String systemId, boolean disAllowDocTypeDeclarations) + throws ParserConfigurationException, SAXException, IOException, XMLStreamException { + XMLStreamReader reader = StaxUtils.createXMLStreamReader(systemId, new FileInputStream(uri), disAllowDocTypeDeclarations); + try { + Document doc = DOMUtils.newDocument(disAllowDocTypeDeclarations); + if (reader.getLocation().getSystemId() != null) { + try { + doc.setDocumentURI(reader.getLocation().getSystemId()); + } catch (Exception e) { + //ignore - probably not DOM level 3 + } + } + StaxUtils.readDocElements(doc, doc, reader, true, false); + return doc; + } finally { + try { + reader.close(); + } catch (Exception ex) { + //ignore + } + } + } + }
diff --git a/src/test/java/org/apache/xml/security/test/dom/c14n/implementations/Canonicalizer11Test.java b/src/test/java/org/apache/xml/security/test/dom/c14n/implementations/Canonicalizer11Test.java index 3522a8b..3a75bab 100644 --- a/src/test/java/org/apache/xml/security/test/dom/c14n/implementations/Canonicalizer11Test.java +++ b/src/test/java/org/apache/xml/security/test/dom/c14n/implementations/Canonicalizer11Test.java
@@ -32,8 +32,8 @@ import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.test.dom.DSNamespaceContext; +import org.apache.xml.security.test.dom.TestUtils; import org.apache.xml.security.utils.JavaUtils; -import org.apache.xml.security.utils.XMLUtils; import org.w3c.dom.Document; import org.w3c.dom.NodeList; @@ -244,7 +244,8 @@ Map<String, String> namespaces ) throws Exception { - Document doc = XMLUtils.read(fileIn, false); + String systemId = "target/test-classes/org/apache/xml/security/c14n/in/xyz"; + Document doc = TestUtils.read(fileIn, systemId, false); Canonicalizer c14n = Canonicalizer.getInstance(c14nURI); byte[] c14nBytes = null;
diff --git a/src/test/java/org/apache/xml/security/test/dom/c14n/implementations/Canonicalizer20010315Test.java b/src/test/java/org/apache/xml/security/test/dom/c14n/implementations/Canonicalizer20010315Test.java index 3116d51..f2d810e 100644 --- a/src/test/java/org/apache/xml/security/test/dom/c14n/implementations/Canonicalizer20010315Test.java +++ b/src/test/java/org/apache/xml/security/test/dom/c14n/implementations/Canonicalizer20010315Test.java
@@ -42,6 +42,7 @@ import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.test.dom.DSNamespaceContext; +import org.apache.xml.security.test.dom.TestUtils; import org.apache.xml.security.utils.JavaUtils; import org.apache.xml.security.utils.XMLUtils; import org.w3c.dom.Document; @@ -710,7 +711,8 @@ Map<String, String> namespaces ) throws Exception { - Document doc = XMLUtils.read(fileIn, false); + String systemId = "target/test-classes/org/apache/xml/security/c14n/in/xyz"; + Document doc = TestUtils.read(fileIn, systemId, false); Canonicalizer c14n = Canonicalizer.getInstance(c14nURI);
diff --git a/src/test/java/org/apache/xml/security/test/dom/interop/InteropTestBase.java b/src/test/java/org/apache/xml/security/test/dom/interop/InteropTestBase.java index 7eaf26b..8dabaec 100644 --- a/src/test/java/org/apache/xml/security/test/dom/interop/InteropTestBase.java +++ b/src/test/java/org/apache/xml/security/test/dom/interop/InteropTestBase.java
@@ -36,6 +36,7 @@ import org.apache.xml.security.signature.reference.ReferenceNodeSetData; import org.apache.xml.security.signature.reference.ReferenceOctetStreamData; import org.apache.xml.security.test.dom.DSNamespaceContext; +import org.apache.xml.security.test.dom.TestUtils; import org.apache.xml.security.utils.XMLUtils; import org.apache.xml.security.utils.resolver.ResourceResolverSpi; import org.w3c.dom.Element; @@ -91,8 +92,13 @@ public boolean verify(String filename, ResourceResolverSpi resolver, boolean followManifests, boolean secureValidation) throws Exception { - File f = new File(filename); - org.w3c.dom.Document doc = XMLUtils.read(new FileInputStream(f), false); + return verify(filename, resolver, null, followManifests, secureValidation); + } + + public boolean verify(String filename, ResourceResolverSpi resolver, String systemId, + boolean followManifests, boolean secureValidation) + throws Exception { + org.w3c.dom.Document doc = TestUtils.read(filename, systemId, false); XPathFactory xpf = XPathFactory.newInstance(); XPath xpath = xpf.newXPath(); @@ -101,6 +107,7 @@ String expression = "//ds:Signature[1]"; Element sigElement = (Element) xpath.evaluate(expression, doc, XPathConstants.NODE); + File f = new File(filename); XMLSignature signature = new XMLSignature(sigElement, f.toURI().toURL().toString(), secureValidation); if (resolver != null) {