blob: 92a1e813686fedc840aae0c5cc33fc763ac3c0aa [file] [log] [blame]
/**
* 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.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ArrayBlockingQueue;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* DOM and XML accessibility and comfort functions.
*
*/
public final class XMLUtils {
private static boolean ignoreLineBreaks =
AccessController.doPrivileged(
(PrivilegedAction<Boolean>) () -> Boolean.getBoolean("org.apache.xml.security.ignoreLineBreaks"));
private static int parserPoolSize =
AccessController.doPrivileged(
(PrivilegedAction<Integer>) () -> Integer.getInteger("org.apache.xml.security.parser.pool-size", 20));
private static volatile String dsPrefix = "ds";
private static volatile String ds11Prefix = "dsig11";
private static volatile String xencPrefix = "xenc";
private static volatile String xenc11Prefix = "xenc11";
private static final org.slf4j.Logger LOG =
org.slf4j.LoggerFactory.getLogger(XMLUtils.class);
private static final Map<ClassLoader, Queue<DocumentBuilder>> DOCUMENT_BUILDERS =
Collections.synchronizedMap(new WeakHashMap<ClassLoader, Queue<DocumentBuilder>>());
private static final Map<ClassLoader, Queue<DocumentBuilder>> DOCUMENT_BUILDERS_DISALLOW_DOCTYPE =
Collections.synchronizedMap(new WeakHashMap<ClassLoader, Queue<DocumentBuilder>>());
/**
* Constructor XMLUtils
*
*/
private XMLUtils() {
// we don't allow instantiation
}
/**
* Set the prefix for the digital signature namespace
* @param prefix the new prefix for the digital signature namespace
* @throws SecurityException if a security manager is installed and the
* caller does not have permission to set the prefix
*/
public static void setDsPrefix(String prefix) {
JavaUtils.checkRegisterPermission();
dsPrefix = prefix;
}
/**
* Set the prefix for the digital signature 1.1 namespace
* @param prefix the new prefix for the digital signature 1.1 namespace
* @throws SecurityException if a security manager is installed and the
* caller does not have permission to set the prefix
*/
public static void setDs11Prefix(String prefix) {
JavaUtils.checkRegisterPermission();
ds11Prefix = prefix;
}
/**
* Set the prefix for the encryption namespace
* @param prefix the new prefix for the encryption namespace
* @throws SecurityException if a security manager is installed and the
* caller does not have permission to set the prefix
*/
public static void setXencPrefix(String prefix) {
JavaUtils.checkRegisterPermission();
xencPrefix = prefix;
}
/**
* Set the prefix for the encryption namespace 1.1
* @param prefix the new prefix for the encryption namespace 1.1
* @throws SecurityException if a security manager is installed and the
* caller does not have permission to set the prefix
*/
public static void setXenc11Prefix(String prefix) {
JavaUtils.checkRegisterPermission();
xenc11Prefix = prefix;
}
public static Element getNextElement(Node el) {
Node node = el;
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
node = node.getNextSibling();
}
return (Element)node;
}
/**
* @param rootNode
* @param result
* @param exclude
* @param com whether comments or not
*/
public static void getSet(Node rootNode, Set<Node> result, Node exclude, boolean com) {
if (exclude != null && isDescendantOrSelf(exclude, rootNode)) {
return;
}
getSetRec(rootNode, result, exclude, com);
}
@SuppressWarnings("fallthrough")
private static void getSetRec(final Node rootNode, final Set<Node> result,
final Node exclude, final boolean com) {
if (rootNode == exclude) {
return;
}
switch (rootNode.getNodeType()) { //NOPMD
case Node.ELEMENT_NODE:
result.add(rootNode);
Element el = (Element)rootNode;
if (el.hasAttributes()) {
NamedNodeMap nl = el.getAttributes();
int length = nl.getLength();
for (int i = 0; i < length; i++) {
result.add(nl.item(i));
}
}
//no return keep working
case Node.DOCUMENT_NODE:
for (Node r = rootNode.getFirstChild(); r != null; r = r.getNextSibling()) {
if (r.getNodeType() == Node.TEXT_NODE) {
result.add(r);
while (r != null && r.getNodeType() == Node.TEXT_NODE) {
r = r.getNextSibling();
}
if (r == null) {
return;
}
}
getSetRec(r, result, exclude, com);
}
break;
case Node.COMMENT_NODE:
if (com) {
result.add(rootNode);
}
break;
case Node.DOCUMENT_TYPE_NODE:
break;
default:
result.add(rootNode);
}
}
/**
* Outputs a DOM tree to an {@link OutputStream}.
*
* @param contextNode root node of the DOM tree
* @param os the {@link OutputStream}
*/
public static void outputDOM(Node contextNode, OutputStream os) {
XMLUtils.outputDOM(contextNode, os, false);
}
/**
* Outputs a DOM tree to an {@link OutputStream}. <I>If an Exception is
* thrown during execution, it's StackTrace is output to System.out, but the
* Exception is not re-thrown.</I>
*
* @param contextNode root node of the DOM tree
* @param os the {@link OutputStream}
* @param addPreamble
*/
public static void outputDOM(Node contextNode, OutputStream os, boolean addPreamble) {
try {
if (addPreamble) {
os.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".getBytes(java.nio.charset.StandardCharsets.UTF_8));
}
os.write(Canonicalizer.getInstance(
Canonicalizer.ALGO_ID_C14N_PHYSICAL).canonicalizeSubtree(contextNode)
);
} catch (IOException ex) {
LOG.debug(ex.getMessage(), ex);
}
catch (InvalidCanonicalizerException ex) {
LOG.debug(ex.getMessage(), ex);
} catch (CanonicalizationException ex) {
LOG.debug(ex.getMessage(), ex);
}
}
/**
* Serializes the <CODE>contextNode</CODE> into the OutputStream, <I>but
* suppresses all Exceptions</I>.
* <p></p>
* NOTE: <I>This should only be used for debugging purposes,
* NOT in a production environment; this method ignores all exceptions,
* so you won't notice if something goes wrong. If you're asking what is to
* be used in a production environment, simply use the code inside the
* <code>try{}</code> statement, but handle the Exceptions appropriately.</I>
*
* @param contextNode
* @param os
*/
public static void outputDOMc14nWithComments(Node contextNode, OutputStream os) {
try {
os.write(Canonicalizer.getInstance(
Canonicalizer.ALGO_ID_C14N_WITH_COMMENTS).canonicalizeSubtree(contextNode)
);
} catch (IOException ex) {
LOG.debug(ex.getMessage(), ex);
// throw new RuntimeException(ex.getMessage());
} catch (InvalidCanonicalizerException ex) {
LOG.debug(ex.getMessage(), ex);
// throw new RuntimeException(ex.getMessage());
} catch (CanonicalizationException ex) {
LOG.debug(ex.getMessage(), ex);
// throw new RuntimeException(ex.getMessage());
}
}
/**
* Method getFullTextChildrenFromNode
*
* @param node
* @return the string of children
*/
public static String getFullTextChildrenFromNode(Node node) {
StringBuilder sb = new StringBuilder();
Node child = node.getFirstChild();
while (child != null) {
if (child.getNodeType() == Node.TEXT_NODE) {
sb.append(((Text)child).getData());
}
child = child.getNextSibling();
}
return sb.toString();
}
/**
* Creates an Element in the XML Signature specification namespace.
*
* @param doc the factory Document
* @param elementName the local name of the Element
* @return the Element
*/
public static Element createElementInSignatureSpace(Document doc, String elementName) {
if (doc == null) {
throw new RuntimeException("Document is null");
}
if (dsPrefix == null || dsPrefix.length() == 0) {
return doc.createElementNS(Constants.SignatureSpecNS, elementName);
}
return doc.createElementNS(Constants.SignatureSpecNS, dsPrefix + ":" + elementName);
}
/**
* Creates an Element in the XML Signature 1.1 specification namespace.
*
* @param doc the factory Document
* @param elementName the local name of the Element
* @return the Element
*/
public static Element createElementInSignature11Space(Document doc, String elementName) {
if (doc == null) {
throw new RuntimeException("Document is null");
}
if (ds11Prefix == null || ds11Prefix.length() == 0) {
return doc.createElementNS(Constants.SignatureSpec11NS, elementName);
}
return doc.createElementNS(Constants.SignatureSpec11NS, ds11Prefix + ":" + elementName);
}
/**
* Creates an Element in the XML Encryption specification namespace.
*
* @param doc the factory Document
* @param elementName the local name of the Element
* @return the Element
*/
public static Element createElementInEncryptionSpace(Document doc, String elementName) {
if (doc == null) {
throw new RuntimeException("Document is null");
}
if (xencPrefix == null || xencPrefix.length() == 0) {
return doc.createElementNS(EncryptionConstants.EncryptionSpecNS, elementName);
}
return
doc.createElementNS(
EncryptionConstants.EncryptionSpecNS, xencPrefix + ":" + elementName
);
}
/**
* Creates an Element in the XML Encryption 1.1 specification namespace.
*
* @param doc the factory Document
* @param elementName the local name of the Element
* @return the Element
*/
public static Element createElementInEncryption11Space(Document doc, String elementName) {
if (doc == null) {
throw new RuntimeException("Document is null");
}
if (xenc11Prefix == null || xenc11Prefix.length() == 0) {
return doc.createElementNS(EncryptionConstants.EncryptionSpec11NS, elementName);
}
return
doc.createElementNS(
EncryptionConstants.EncryptionSpec11NS, xenc11Prefix + ":" + elementName
);
}
/**
* Returns true if the element is in XML Signature namespace and the local
* name equals the supplied one.
*
* @param element
* @param localName
* @return true if the element is in XML Signature namespace and the local name equals
* the supplied one
*/
public static boolean elementIsInSignatureSpace(Element element, String localName) {
if (element == null){
return false;
}
return Constants.SignatureSpecNS.equals(element.getNamespaceURI())
&& element.getLocalName().equals(localName);
}
/**
* Returns true if the element is in XML Signature 1.1 namespace and the local
* name equals the supplied one.
*
* @param element
* @param localName
* @return true if the element is in XML Signature namespace and the local name equals
* the supplied one
*/
public static boolean elementIsInSignature11Space(Element element, String localName) {
if (element == null) {
return false;
}
return Constants.SignatureSpec11NS.equals(element.getNamespaceURI())
&& element.getLocalName().equals(localName);
}
/**
* Returns true if the element is in XML Encryption namespace and the local
* name equals the supplied one.
*
* @param element
* @param localName
* @return true if the element is in XML Encryption namespace and the local name
* equals the supplied one
*/
public static boolean elementIsInEncryptionSpace(Element element, String localName) {
if (element == null){
return false;
}
return EncryptionConstants.EncryptionSpecNS.equals(element.getNamespaceURI())
&& element.getLocalName().equals(localName);
}
/**
* Returns true if the element is in XML Encryption 1.1 namespace and the local
* name equals the supplied one.
*
* @param element
* @param localName
* @return true if the element is in XML Encryption 1.1 namespace and the local name
* equals the supplied one
*/
public static boolean elementIsInEncryption11Space(Element element, String localName) {
if (element == null){
return false;
}
return EncryptionConstants.EncryptionSpec11NS.equals(element.getNamespaceURI())
&& element.getLocalName().equals(localName);
}
/**
* This method returns the owner document of a particular node.
* This method is necessary because it <I>always</I> returns a
* {@link Document}. {@link Node#getOwnerDocument} returns <CODE>null</CODE>
* if the {@link Node} is a {@link Document}.
*
* @param node
* @return the owner document of the node
*/
public static Document getOwnerDocument(Node node) {
if (node.getNodeType() == Node.DOCUMENT_NODE) {
return (Document) node;
}
try {
return node.getOwnerDocument();
} catch (NullPointerException npe) {
throw new NullPointerException(I18n.translate("endorsed.jdk1.4.0")
+ " Original message was \""
+ npe.getMessage() + "\"");
}
}
/**
* This method returns the first non-null owner document of the Nodes in this Set.
* This method is necessary because it <I>always</I> returns a
* {@link Document}. {@link Node#getOwnerDocument} returns <CODE>null</CODE>
* if the {@link Node} is a {@link Document}.
*
* @param xpathNodeSet
* @return the owner document
*/
public static Document getOwnerDocument(Set<Node> xpathNodeSet) {
NullPointerException npe = null;
for (Node node : xpathNodeSet) {
int nodeType = node.getNodeType();
if (nodeType == Node.DOCUMENT_NODE) {
return (Document) node;
}
try {
if (nodeType == Node.ATTRIBUTE_NODE) {
return ((Attr)node).getOwnerElement().getOwnerDocument();
}
return node.getOwnerDocument();
} catch (NullPointerException e) {
npe = e;
}
}
throw new NullPointerException(I18n.translate("endorsed.jdk1.4.0")
+ " Original message was \""
+ (npe == null ? "" : npe.getMessage()) + "\"");
}
/**
* Method createDSctx
*
* @param doc
* @param prefix
* @param namespace
* @return the element.
*/
public static Element createDSctx(Document doc, String prefix, String namespace) {
if (prefix == null || prefix.trim().length() == 0) {
throw new IllegalArgumentException("You must supply a prefix");
}
Element ctx = doc.createElementNS(null, "namespaceContext");
ctx.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:" + prefix.trim(), namespace);
return ctx;
}
/**
* Method addReturnToElement
*
* @param e
*/
public static void addReturnToElement(Element e) {
if (!ignoreLineBreaks) {
Document doc = e.getOwnerDocument();
e.appendChild(doc.createTextNode("\n"));
}
}
public static void addReturnToElement(Document doc, HelperNodeList nl) {
if (!ignoreLineBreaks) {
nl.appendChild(doc.createTextNode("\n"));
}
}
public static void addReturnBeforeChild(Element e, Node child) {
if (!ignoreLineBreaks) {
Document doc = e.getOwnerDocument();
e.insertBefore(doc.createTextNode("\n"), child);
}
}
public static String encodeToString(byte[] bytes) {
if (ignoreLineBreaks) {
return Base64.getEncoder().encodeToString(bytes);
}
return Base64.getMimeEncoder().encodeToString(bytes);
}
public static byte[] decode(String encodedString) {
return Base64.getMimeDecoder().decode(encodedString);
}
public static byte[] decode(byte[] encodedBytes) {
return Base64.getMimeDecoder().decode(encodedBytes);
}
public static boolean isIgnoreLineBreaks() {
return ignoreLineBreaks;
}
/**
* Method convertNodelistToSet
*
* @param xpathNodeSet
* @return the set with the nodelist
*/
public static Set<Node> convertNodelistToSet(NodeList xpathNodeSet) {
if (xpathNodeSet == null) {
return new HashSet<>();
}
int length = xpathNodeSet.getLength();
Set<Node> set = new HashSet<>(length);
for (int i = 0; i < length; i++) {
set.add(xpathNodeSet.item(i));
}
return set;
}
/**
* This method spreads all namespace attributes in a DOM document to their
* children. This is needed because the XML Signature XPath transform
* must evaluate the XPath against all nodes in the input, even against
* XPath namespace nodes. Through a bug in XalanJ2, the namespace nodes are
* not fully visible in the Xalan XPath model, so we have to do this by
* hand in DOM spaces so that the nodes become visible in XPath space.
*
* @param doc
* @see <A HREF="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=2650">
* Namespace axis resolution is not XPath compliant </A>
*/
public static void circumventBug2650(Document doc) {
Element documentElement = doc.getDocumentElement();
// if the document element has no xmlns definition, we add xmlns=""
Attr xmlnsAttr =
documentElement.getAttributeNodeNS(Constants.NamespaceSpecNS, "xmlns");
if (xmlnsAttr == null) {
documentElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "");
}
XMLUtils.circumventBug2650internal(doc);
}
/**
* This is the work horse for {@link #circumventBug2650}.
*
* @param node
* @see <A HREF="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=2650">
* Namespace axis resolution is not XPath compliant </A>
*/
@SuppressWarnings("fallthrough")
private static void circumventBug2650internal(Node node) {
Node parent = null;
Node sibling = null;
final String namespaceNs = Constants.NamespaceSpecNS;
do {
switch (node.getNodeType()) {
case Node.ELEMENT_NODE :
Element element = (Element) node;
if (!element.hasChildNodes()) {
break;
}
if (element.hasAttributes()) {
NamedNodeMap attributes = element.getAttributes();
int attributesLength = attributes.getLength();
for (Node child = element.getFirstChild(); child!=null;
child = child.getNextSibling()) {
if (child.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element childElement = (Element) child;
for (int i = 0; i < attributesLength; i++) {
Attr currentAttr = (Attr) attributes.item(i);
if (!namespaceNs.equals(currentAttr.getNamespaceURI())) {
continue;
}
if (childElement.hasAttributeNS(namespaceNs,
currentAttr.getLocalName())) {
continue;
}
childElement.setAttributeNS(namespaceNs,
currentAttr.getName(),
currentAttr.getNodeValue());
}
}
}
case Node.ENTITY_REFERENCE_NODE :
case Node.DOCUMENT_NODE :
parent = node;
sibling = node.getFirstChild();
break;
}
while (sibling == null && parent != null) {
sibling = parent.getNextSibling();
parent = parent.getParentNode();
}
if (sibling == null) {
return;
}
node = sibling;
sibling = node.getNextSibling();
} while (true);
}
/**
* @param sibling
* @param nodeName
* @param number
* @return nodes with the constraint
*/
public static Element selectDsNode(Node sibling, String nodeName, int number) {
while (sibling != null) {
if (Constants.SignatureSpecNS.equals(sibling.getNamespaceURI())
&& sibling.getLocalName().equals(nodeName)) {
if (number == 0){
return (Element)sibling;
}
number--;
}
sibling = sibling.getNextSibling();
}
return null;
}
/**
* @param sibling
* @param nodeName
* @param number
* @return nodes with the constraint
*/
public static Element selectDs11Node(Node sibling, String nodeName, int number) {
while (sibling != null) {
if (Constants.SignatureSpec11NS.equals(sibling.getNamespaceURI())
&& sibling.getLocalName().equals(nodeName)) {
if (number == 0){
return (Element)sibling;
}
number--;
}
sibling = sibling.getNextSibling();
}
return null;
}
/**
* @param sibling
* @param nodeName
* @param number
* @return nodes with the constrain
*/
public static Element selectXencNode(Node sibling, String nodeName, int number) {
while (sibling != null) {
if (EncryptionConstants.EncryptionSpecNS.equals(sibling.getNamespaceURI())
&& sibling.getLocalName().equals(nodeName)) {
if (number == 0){
return (Element)sibling;
}
number--;
}
sibling = sibling.getNextSibling();
}
return null;
}
/**
* @param sibling
* @param uri
* @param nodeName
* @param number
* @return nodes with the constrain
*/
public static Element selectNode(Node sibling, String uri, String nodeName, int number) {
while (sibling != null) {
if (sibling.getNamespaceURI() != null && sibling.getNamespaceURI().equals(uri)
&& sibling.getLocalName().equals(nodeName)) {
if (number == 0) {
return (Element)sibling;
}
number--;
}
sibling = sibling.getNextSibling();
}
return null;
}
/**
* @param sibling
* @param nodeName
* @return nodes with the constrain
*/
public static Element[] selectDsNodes(Node sibling, String nodeName) {
return selectNodes(sibling, Constants.SignatureSpecNS, nodeName);
}
/**
* @param sibling
* @param nodeName
* @return nodes with the constrain
*/
public static Element[] selectDs11Nodes(Node sibling, String nodeName) {
return selectNodes(sibling, Constants.SignatureSpec11NS, nodeName);
}
/**
* @param sibling
* @param uri
* @param nodeName
* @return nodes with the constraint
*/
public static Element[] selectNodes(Node sibling, String uri, String nodeName) {
List<Element> list = new ArrayList<>();
while (sibling != null) {
if (sibling.getNamespaceURI() != null && sibling.getNamespaceURI().equals(uri)
&& sibling.getLocalName().equals(nodeName)) {
list.add((Element)sibling);
}
sibling = sibling.getNextSibling();
}
return list.toArray(new Element[list.size()]);
}
/**
* @param signatureElement
* @param inputSet
* @return nodes with the constrain
*/
public static Set<Node> excludeNodeFromSet(Node signatureElement, Set<Node> inputSet) {
Set<Node> resultSet = new HashSet<>();
Iterator<Node> iterator = inputSet.iterator();
while (iterator.hasNext()) {
Node inputNode = iterator.next();
if (!XMLUtils.isDescendantOrSelf(signatureElement, inputNode)) {
resultSet.add(inputNode);
}
}
return resultSet;
}
/**
* Method getStrFromNode
*
* @param xpathnode
* @return the string for the node.
*/
public static String getStrFromNode(Node xpathnode) {
if (xpathnode.getNodeType() == Node.TEXT_NODE) {
// we iterate over all siblings of the context node because eventually,
// the text is "polluted" with pi's or comments
StringBuilder sb = new StringBuilder();
for (Node currentSibling = xpathnode.getParentNode().getFirstChild();
currentSibling != null;
currentSibling = currentSibling.getNextSibling()) {
if (currentSibling.getNodeType() == Node.TEXT_NODE) {
sb.append(((Text) currentSibling).getData());
}
}
return sb.toString();
} else if (xpathnode.getNodeType() == Node.ATTRIBUTE_NODE) {
return xpathnode.getNodeValue();
} else if (xpathnode.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
return xpathnode.getNodeValue();
}
return null;
}
/**
* Returns true if the descendantOrSelf is on the descendant-or-self axis
* of the context node.
*
* @param ctx
* @param descendantOrSelf
* @return true if the node is descendant
*/
public static boolean isDescendantOrSelf(Node ctx, Node descendantOrSelf) {
if (ctx == descendantOrSelf) {
return true;
}
Node parent = descendantOrSelf;
while (true) {
if (parent == null) {
return false;
}
if (parent == ctx) {
return true;
}
if (parent.getNodeType() == Node.ATTRIBUTE_NODE) {
parent = ((Attr) parent).getOwnerElement();
} else {
parent = parent.getParentNode();
}
}
}
public static boolean ignoreLineBreaks() {
return ignoreLineBreaks;
}
/**
* Returns the attribute value for the attribute with the specified name.
* Returns null if there is no such attribute, or
* the empty string if the attribute value is empty.
*
* <p>This works around a limitation of the DOM
* <code>Element.getAttributeNode</code> method, which does not distinguish
* between an unspecified attribute and an attribute with a value of
* "" (it returns "" for both cases).
*
* @param elem the element containing the attribute
* @param name the name of the attribute
* @return the attribute value (may be null if unspecified)
*/
public static String getAttributeValue(Element elem, String name) {
Attr attr = elem.getAttributeNodeNS(null, name);
return (attr == null) ? null : attr.getValue();
}
/**
* This method is a tree-search to help prevent against wrapping attacks. It checks that no
* two Elements have ID Attributes that match the "value" argument, if this is the case then
* "false" is returned. Note that a return value of "true" does not necessarily mean that
* a matching Element has been found, just that no wrapping attack has been detected.
*/
public static boolean protectAgainstWrappingAttack(Node startNode, String value) {
String id = value.trim();
if (!id.isEmpty() && id.charAt(0) == '#') {
id = id.substring(1);
}
Node startParent = null;
Node processedNode = null;
Element foundElement = null;
if (startNode != null) {
startParent = startNode.getParentNode();
}
while (startNode != null) {
if (startNode.getNodeType() == Node.ELEMENT_NODE) {
Element se = (Element) startNode;
NamedNodeMap attributes = se.getAttributes();
if (attributes != null) {
int length = attributes.getLength();
for (int i = 0; i < length; i++) {
Attr attr = (Attr)attributes.item(i);
if (attr.isId() && id.equals(attr.getValue())) {
if (foundElement == null) {
// Continue searching to find duplicates
foundElement = attr.getOwnerElement();
} else {
LOG.debug("Multiple elements with the same 'Id' attribute value!");
return false;
}
}
}
}
}
processedNode = startNode;
startNode = startNode.getFirstChild();
// no child, this node is done.
if (startNode == null) {
// close node processing, get sibling
startNode = processedNode.getNextSibling();
}
// no more siblings, get parent, all children
// of parent are processed.
while (startNode == null) {
processedNode = processedNode.getParentNode();
if (processedNode == startParent) {
return true;
}
// close parent node processing (processed node now)
startNode = processedNode.getNextSibling();
}
}
return true;
}
/**
* This method is a tree-search to help prevent against wrapping attacks. It checks that no other
* Element than the given "knownElement" argument has an ID attribute that matches the "value"
* argument, which is the ID value of "knownElement". If this is the case then "false" is returned.
*/
public static boolean protectAgainstWrappingAttack(
Node startNode, Element knownElement, String value
) {
String id = value.trim();
if (!id.isEmpty() && id.charAt(0) == '#') {
id = id.substring(1);
}
Node startParent = null;
Node processedNode = null;
if (startNode != null) {
startParent = startNode.getParentNode();
}
while (startNode != null) {
if (startNode.getNodeType() == Node.ELEMENT_NODE) {
Element se = (Element) startNode;
NamedNodeMap attributes = se.getAttributes();
if (attributes != null) {
int length = attributes.getLength();
for (int i = 0; i < length; i++) {
Attr attr = (Attr)attributes.item(i);
if (attr.isId() && id.equals(attr.getValue()) && se != knownElement) {
LOG.debug("Multiple elements with the same 'Id' attribute value!");
return false;
}
}
}
}
processedNode = startNode;
startNode = startNode.getFirstChild();
// no child, this node is done.
if (startNode == null) {
// close node processing, get sibling
startNode = processedNode.getNextSibling();
}
// no more siblings, get parent, all children
// of parent are processed.
while (startNode == null) {
processedNode = processedNode.getParentNode();
if (processedNode == startParent) {
return true;
}
// close parent node processing (processed node now)
startNode = processedNode.getNextSibling();
}
}
return true;
}
public static Document newDocument() throws ParserConfigurationException {
DocumentBuilder documentBuilder = getDocumentBuilder(true);
Document doc = documentBuilder.newDocument();
repoolDocumentBuilder(documentBuilder, true);
return doc;
}
public static Document read(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException {
return read(inputStream, true);
}
public static Document read(InputStream inputStream, boolean disAllowDocTypeDeclarations) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilder documentBuilder = getDocumentBuilder(disAllowDocTypeDeclarations);
Document doc = documentBuilder.parse(inputStream);
repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations);
return doc;
}
public static Document read(String uri, boolean disAllowDocTypeDeclarations)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilder documentBuilder = getDocumentBuilder(disAllowDocTypeDeclarations);
Document doc = documentBuilder.parse(uri);
repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations);
return doc;
}
public static Document read(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException {
return read(inputSource, true);
}
public static Document read(InputSource inputSource, boolean disAllowDocTypeDeclarations)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilder documentBuilder = getDocumentBuilder(disAllowDocTypeDeclarations);
Document doc = documentBuilder.parse(inputSource);
repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations);
return doc;
}
/**
* Returns a byte-array representation of a <code>{@link BigInteger}</code>.
* No sign-bit is output.
*
* <b>N.B.:</B> <code>{@link BigInteger}</code>'s toByteArray
* returns eventually longer arrays because of the leading sign-bit.
*
* @param big <code>BigInteger</code> to be converted
* @param bitlen <code>int</code> the desired length in bits of the representation
* @return a byte array with <code>bitlen</code> bits of <code>big</code>
*/
public static byte[] getBytes(BigInteger big, int bitlen) {
//round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
if (bitlen < big.bitLength()) {
throw new IllegalArgumentException(I18n.translate("utils.Base64.IllegalBitlength"));
}
byte[] bigBytes = big.toByteArray();
if (big.bitLength() % 8 != 0
&& big.bitLength() / 8 + 1 == bitlen / 8) {
return bigBytes;
}
// some copying needed
int startSrc = 0; // no need to skip anything
int bigLen = bigBytes.length; //valid length of the string
if (big.bitLength() % 8 == 0) { // correct values
startSrc = 1; // skip sign bit
bigLen--; // valid length of the string
}
int startDst = bitlen / 8 - bigLen; //pad with leading nulls
byte[] resizedBytes = new byte[bitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, bigLen);
return resizedBytes;
}
private static DocumentBuilder getDocumentBuilder(boolean disAllowDocTypeDeclarations) throws ParserConfigurationException {
ClassLoader loader = getContextClassLoader();
if (loader == null) {
loader = getClassLoader(XMLUtils.class);
}
if (loader == null) {
return createDocumentBuilder(disAllowDocTypeDeclarations);
}
Map<ClassLoader, Queue<DocumentBuilder>> docBuilderCache =
disAllowDocTypeDeclarations ? DOCUMENT_BUILDERS_DISALLOW_DOCTYPE : DOCUMENT_BUILDERS;
Queue<DocumentBuilder> queue = docBuilderCache.get(loader);
if (queue == null) {
queue = new ArrayBlockingQueue<>(parserPoolSize);
docBuilderCache.put(loader, queue);
}
DocumentBuilder db = queue.poll();
if (db == null) {
db = createDocumentBuilder(disAllowDocTypeDeclarations);
}
return db;
}
private static DocumentBuilder createDocumentBuilder(boolean disAllowDocTypeDeclarations) throws ParserConfigurationException {
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();
}
private static void repoolDocumentBuilder(DocumentBuilder db, boolean disAllowDocTypeDeclarations) {
ClassLoader loader = getContextClassLoader();
if (loader == null) {
loader = getClassLoader(XMLUtils.class);
}
if (loader != null) {
Queue<DocumentBuilder> queue =
disAllowDocTypeDeclarations ? DOCUMENT_BUILDERS_DISALLOW_DOCTYPE.get(loader) : DOCUMENT_BUILDERS.get(loader);
if (queue != null) {
db.reset();
queue.offer(db);
}
}
}
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();
}
}