org.xwiki.xml.XMLUtils Maven / Gradle / Ivy
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.xml;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Objects;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jdom2.input.DOMBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSParser;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xwiki.xml.internal.DefaultXMLReaderFactory;
/**
* XML Utility methods.
*
* @version $Id: 05a3e981852ebdbf2e8708fe6ab4a5cb876d75ca $
* @since 1.6M1
*/
// Utility class with lots of features
@SuppressWarnings("checkstyle:ClassFanOutComplexity")
public final class XMLUtils
{
/**
* A simple error logging helper to suppress expected error messages.
* While extracting text excerpts via the {@link #extractXML} method
* the extraction throws expected exceptions eg. in case a text node
* is too long. The default ErrorListener writes these errors to the console.
* However as these errors are expected under normal conditions
* and are either handled in the {@link #extractXML} method
* or passed up in the chain of commands it is ok to log these
* at a much lower level, where users normally never see them.
*/
private static final class RelaxedErrorListener implements ErrorListener
{
private static final String STACK_TRACE_NOTE = "stack trace for information only";
/**
* Fatal errors are unexpected and are logged as warnings here.
* They are not really fatal to XWiki but should be handled
* up the command chain later, or maybe propagated to the UI level.
*
* @param exception the exception to be logged
*/
@Override
public void fatalError(TransformerException exception) throws TransformerException
{
LOGGER.warn("Fatal error from xml transformer: [{}]", ExceptionUtils.getRootCauseMessage(exception));
LOGGER.trace(STACK_TRACE_NOTE, exception);
}
/**
* Errors are expected and are only logged as debug.
* These errors happen e.g. if a text node exceeds the expected
* maximal length, which can happen in regular usage.
*
* @param exception the exception to be logged
*/
@Override
public void error(TransformerException exception) throws TransformerException
{
LOGGER.debug("Error [{}] from xml transformer", ExceptionUtils.getRootCauseMessage(exception));
LOGGER.trace(STACK_TRACE_NOTE, exception);
}
/**
* Warnings are logged at debug level.
* They might be of concern for the developers but not the end users.
*
* @param exception the exception to be logged
*/
@Override
public void warning(TransformerException exception) throws TransformerException
{
LOGGER.debug("Warning [{}] from xml transformer", ExceptionUtils.getRootCauseMessage(exception));
LOGGER.trace(STACK_TRACE_NOTE, exception);
}
}
/** Logging helper object. */
private static final Logger LOGGER = LoggerFactory.getLogger(XMLUtils.class);
/** XML encoding of the "ampersand" character. */
private static final String AMP = "&";
/** Regular expression recognizing XML-escaped "ampersand" characters. */
private static final Pattern AMP_PATTERN = Pattern.compile("&(?:amp|#0*+38|#x0*+26);");
/** XML encoding of the "single quote" character. */
private static final String APOS = "'";
/** Regular expression recognizing XML-escaped "single quote" characters. */
private static final Pattern APOS_PATTERN = Pattern.compile("&(?:apos|#0*+39|#x0*+27);");
/** XML encoding of the "double quote" character. */
private static final String QUOT = """;
/** Regular expression recognizing XML-escaped "double quote" characters. */
private static final Pattern QUOT_PATTERN = Pattern.compile("&(?:quot|#0*+34|#x0*+22);");
/** XML encoding of the "left curly bracket". */
private static final String LCURL = "{";
/** Regular expression recognizing XML-escaped "left curly bracket" characters. */
private static final Pattern LCURL_PATTERN = Pattern.compile("&(?:#0*+123|#x0*+7[bB]);");
/** XML encoding of the "less than" character. */
private static final String LT = "<";
/** Regular expression recognizing XML-escaped "less than" characters. */
private static final Pattern LT_PATTERN = Pattern.compile("&(?:lt|#0*+60|#x0*+3[cC]);");
/** XML encoding of the "greater than" character. */
private static final String GT = ">";
/** Regular expression recognizing XML-escaped "greater than" characters. */
private static final Pattern GT_PATTERN = Pattern.compile("&(?:gt|#0*+62|#x0*+3[eE]);");
private static final char[] ELEMENT_SYNTAX = new char[] {'<', '&'};
/** Helper object for manipulating DOM Level 3 Load and Save APIs. */
private static final DOMImplementationLS LS_IMPL;
/** Xerces configuration parameter for disabling fetching and checking XMLs against their DTD. */
private static final String DISABLE_DTD_PARAM = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
/** Xerces configuration parameter for prevent DOCTYPE definition. */
private static final String DISABLE_EXTERNAL_DOCTYPE_DECLARATION =
"http://apache.org/xml/features/disallow-doctype-decl";
/** Xerces configuration parameter for disabling inserting entities defined in external files. */
private static final String DISABLE_EXTERNAL_PARAMETER_ENTITIES =
"http://xml.org/sax/features/external-parameter-entities";
/** Xerces configuration parameter for disabling inserting entities defined in external files. */
private static final String DISABLE_EXTERNAL_GENERAL_ENTITIES =
"http://xml.org/sax/features/external-general-entities";
/** Helper to log expected errors at an appropriate level. */
private static final ErrorListener RELAXED_ERROR_LISTENER = new RelaxedErrorListener();
private static final String NEWLINE = "\n";
private static final DefaultXMLReaderFactory XML_READER_FACTORY = new DefaultXMLReaderFactory();
static {
DOMImplementationLS implementation = null;
try {
implementation =
(DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS 3.0");
} catch (Exception ex) {
LOGGER.warn("Cannot initialize the XML Script Service: [{}]", ex.getMessage());
}
LS_IMPL = implementation;
XML_READER_FACTORY.initialize();
}
/**
* Private constructor since this is a utility class that shouldn't be instantiated (all methods are static).
*/
private XMLUtils()
{
// Nothing to do
}
/**
* Extracts a well-formed XML fragment from the given DOM tree.
*
* @param node the root of the DOM tree where the extraction takes place
* @param start the index of the first character
* @param length the maximum number of characters in text nodes to include in the returned fragment
* @return a well-formed XML fragment starting at the given character index and having up to the specified length,
* summing only the characters in text nodes
* @since 1.6M2
*/
public static String extractXML(Node node, int start, int length)
{
ExtractHandler handler = null;
try {
handler = new ExtractHandler(start, length);
Transformer xformer = XMLUtils.createTransformerFactory().newTransformer();
xformer.setErrorListener(RELAXED_ERROR_LISTENER);
xformer.transform(new DOMSource(node), new SAXResult(handler));
return handler.getResult();
} catch (Throwable t) {
if (handler != null && handler.isFinished()) {
return handler.getResult();
} else {
throw new RuntimeException("Failed to extract XML", t);
}
}
}
/**
* XML comment does not support some characters inside its content but there is no official escaping/unescaping for
* it so we made our own.
*
* - 1) Escape existing \
* - 2) Escape --
* - 3) Escape > or - at the start of the comment
* - 4) Escape { to prevent XWiki macro syntax
* - 5) Add {@code \} (unescaped as {@code ""}) at the end if the last char is {@code -}
* - 6) Ensure that {@code <} is always followed by {@code \} to avoid matching as HTML tag, e.g., in
* CKEditor
*
*
* @param content the XML comment content to escape
* @return the escaped content.
* @since 1.9M2
*/
public static String escapeXMLComment(String content)
{
StringBuilder str = new StringBuilder(content.length());
char[] buff = content.toCharArray();
// At the start of a comment, > isn't allowed.
if (buff.length > 0 && buff[0] == '>') {
str.append('\\');
}
// Initialize with '-', as "->" isn't allowed at the start of the comment. It is thus better to start with
// an escape when the comment starts with '-'.
char lastChar = '-';
for (char c : buff) {
appendCommentEscapedCharacter(c, lastChar, str);
lastChar = c;
}
if (lastChar == '-' || lastChar == '<') {
// If the comment data ends with '-' or '<', add an escaping character to be on the safe side.
str.append('\\');
}
return str.toString();
}
private static void appendCommentEscapedCharacter(char character, char lastCharacter, StringBuilder result)
{
// The characters '\' and '{' always need to be escaped. The former as it is the escape character, the
// latter because of its special meaning in XWiki syntax that would allow to, e.g., close HTML macros.
boolean needsEscaping = character == '\\' || character == '{';
// Also add an escaping between any two '-' to avoid syntax that is illegal in comments and escape after
// '<' to avoid any matching of comment contents as HTML tags, e.g., by CKEditor.
if (needsEscaping || (character == '-' && lastCharacter == '-') || lastCharacter == '<') {
result.append('\\');
}
result.append(character);
}
/**
* XML comment does not support some characters inside its content but there is no official escaping/unescaping for
* it so we made our own.
*
* @param content the XML comment content to unescape
* @return the unescaped content.
* @see #escapeXMLComment(String)
* @since 1.9M2
*/
public static String unescapeXMLComment(String content)
{
StringBuffer str = new StringBuffer(content.length());
char[] buff = content.toCharArray();
boolean escaped = false;
for (char c : buff) {
if (!escaped && c == '\\') {
escaped = true;
continue;
}
str.append(c);
escaped = false;
}
return str.toString();
}
/**
* Escapes all the XML special characters and a XWiki Syntax 2.0+ special character (i.e., {
, to
* protect against {{/html}}
) in a {@code String}.
* The escaping is done using numerical XML entities to allow the content to be used as an XML attribute value
* or as an XML element text.
* For instance, {@code {{html}}$x{{/html}}} will be escaped and can thus be put inside an XML attribute.
* To illustrate, the value can be used in a div tag
* <div><b>{{html}}$x{{/html}}</b>
* </div>
* or in the attribute of an input tag
* <input
* value="<b>{{html}}$x{{/html}}</b>"
* />
.
*
* Specifically, escapes <, >, ", ', & and {.
*
* Note that is is preferable to use {@link #escapeAttributeValue(String)} when the content is used as
* an XML tag attribute, and {@link #escapeElementText(String)} when the content is used as an XML text.
*
* @param content the text to escape, may be {@code null}. The content is converted to {@code String} using
* {@link Objects#toString(Object, String)}, where the second parameter is {@code null}
* @return a new escaped {@code String}, {@code null} if {@code null} input
* @see #escapeAttributeValue(String)
* @see #escapeElementText(String)
* @deprecated since 12.8RC1, use {@link #escape(String)} instead
*/
@Deprecated
public static String escape(Object content)
{
return escape(Objects.toString(content, null));
}
/**
* Escapes all the XML special characters and a XWiki Syntax 2.0+ special character (i.e., {
, to
* protect against {{/html}}) in a {@code String}.
* The escaping is done using numerical XML entities to allow the content to be used as an XML attribute value
* or as an XML element text.
* For instance, {@code {{html}}$x{{/html}}} will be escaped and can thus be put inside as XML attribute.
* To illustrate, the value can be used in a div tag
* <div><b>{{html}}$x{{/html}}</b>
* </div>
* or in the attribute of an input tag
* <input
* value="<b>{{html}}$x{{/html}}</b>"
* />
.
*
* Specifically, escapes <, >, ", ', & and {.
*
* Note that is is preferable to use {@link #escapeAttributeValue(String)} when the content is used as
* an XML tag attribute, and {@link #escapeElementText(String)} when the content is used as an XML text.
*
* @param content the text to escape, may be {@code null}
* @return a new escaped {@code String}, {@code null} if {@code null} input
* @see #escapeAttributeValue(String)
* @see #escapeElementText(String)
* @since 12.8RC1
* @since 12.6.3
* @since 11.10.11
*/
public static String escape(String content)
{
return escapeAttributeValue(content);
}
/**
* Escapes all the XML special characters and a XWiki Syntax 2.0+ special character (i.e., {
, to
* protect against {{/html}}) in a {@code String}.
* The escaping is done using numerical XML entities to allow the content to be used inside XML attributes.
* For instance, {@code {{html}}$x{{/html}}} will be escaped and can thus be put inside an XML attribute.
* To illustrate, the value can be used in the attribute of an input tag
* <input
* value="<b>{{html}}$x{{/html}}</b>"
* />
.
*
* Specifically, escapes <, >, ", ', & and {.
*
* @param content the text to escape, may be {@code null}. The content is converted to {@code String} using
* {@link String#valueOf(Object)} before escaping.
* @return a new escaped {@code String}, {@code null} if {@code null} input
* @deprecated since 12.8RC1, use {@link #escapeAttributeValue(String)} instead
*/
@Deprecated
public static String escapeAttributeValue(Object content)
{
if (content == null) {
return null;
}
return escapeAttributeValue(String.valueOf(content));
}
/**
* Escapes all the XML special characters and a XWiki Syntax 2.0+ special character (i.e., {
, to
* protect against {{/html}}) in a {@code String}.
* The escaping is done using numerical XML entities to allow the content to be used inside XML attributes.
* For instance, {@code {{html}}$x{{/html}}} will be escaped and can thus be put inside an XML attribute.
* To illustrate, the value can be used in the attribute of an input tag
* <input
* value="<b>{{html}}$x{{/html}}</b>"
* />
.
*
* Specifically, escapes <, >, ", ', & and {.
*
* @param content the text to escape, may be {@code null}
* @return a new escaped {@code String}, {@code null} if {@code null} input
* @since 12.8RC1
* @since 12.6.3
* @since 11.10.11
*/
public static String escapeAttributeValue(String content)
{
if (content == null) {
return null;
}
StringBuilder result = new StringBuilder((int) (content.length() * 1.1));
int length = content.length();
char c;
for (int i = 0; i < length; ++i) {
c = content.charAt(i);
switch (c) {
case '&':
result.append(AMP);
break;
case '\'':
result.append(APOS);
break;
case '"':
result.append(QUOT);
break;
case '<':
result.append(LT);
break;
case '>':
result.append(GT);
break;
case '{':
// Not needed from XML point of view but escaping xwiki/2.x macro syntax helps avoid countless
// security problems easily
result.append(LCURL);
break;
default:
result.append(c);
}
}
return result.toString();
}
/**
* Escapes XML special characters in a {@code String} using numerical XML entities, so that the resulting string
* can safely be used as an XML element text value.
* For instance, {@code Jim & John} will be escaped and can thus be put inside an XML tag, such as the {@code p}
* tag, as in {@code
Jim & John
}.
* Specifically, escapes < to {@code <}, and & to {@code &}.
*
* Since 13.10.9, 14.4.4 and 14.7RC1 the character { is also escaped.
*
* @param content the text to escape, may be {@code null}.
* @return a new escaped {@code String}, {@code null} if {@code null} input
* @since 12.8RC1
* @since 12.6.3
* @since 11.10.11
*/
public static String escapeElementText(String content)
{
if (content == null) {
return null;
}
// Initializes a string builder with an initial capacity 1.1 times greater than the initial content to account
// for special character substitutions.
int contentLength = content.length();
StringBuilder result = new StringBuilder((int) (contentLength * 1.1));
for (int i = 0; i < contentLength; ++i) {
char c = content.charAt(i);
switch (c) {
case '&':
result.append(AMP);
break;
case '<':
result.append(LT);
break;
case '{':
// Not needed from XML point of view but escaping xwiki/2.x macro syntax helps avoid countless
// security problems easily
result.append(LCURL);
break;
default:
result.append(c);
}
}
return result.toString();
}
/**
* Same logic as {@link #escapeElementText(String)} but only indicate if there is something to escape.
*
* @param content the content to parse
* @return true if the passed content contains content that can be interpreted as XML syntax
* @see #escapeElementText(String)
* @since 12.10
* @since 12.6.5
*/
public static boolean containsElementText(CharSequence content)
{
return StringUtils.containsAny(content, ELEMENT_SYNTAX);
}
/**
* Escapes the XML special characters in a String
using numerical XML entities, so that the resulting
* string can safely be used as an XML text node. Specifically, escapes <, >, and &.
*
* @param content the text to escape, may be {@code null}. The content is converted to {@code String} using
* {@link String#valueOf(Object)} before escaping.
* @return a new escaped {@code String}, {@code null} if {@code null} input
* @deprecated since 12.8RC1, use {@link #escapeElementText(String)} instead.
*/
@Deprecated
public static String escapeElementContent(Object content)
{
if (content == null) {
return null;
}
String str = String.valueOf(content);
StringBuilder result = new StringBuilder((int) (str.length() * 1.1));
int length = str.length();
char c;
for (int i = 0; i < length; ++i) {
c = str.charAt(i);
switch (c) {
case '&':
result.append(AMP);
break;
case '<':
result.append(LT);
break;
case '>':
result.append(GT);
break;
default:
result.append(c);
}
}
return result.toString();
}
/**
* Unescape encoded special XML characters. Only >, < &, ", ' and { are unescaped, since they are the only
* ones that affect the resulting markup.
*
* @param content the text to decode, may be {@code null}. The content is converted to {@code String} using
* {@link String#valueOf(Object)} before escaping
* @return unescaped content, {@code null} if {@code null} input
* @deprecated since 12.8RC1, use {@link org.apache.commons.text.StringEscapeUtils#unescapeXml(String)} instead
*/
@Deprecated
public static String unescape(Object content)
{
if (content == null) {
return null;
}
String str = String.valueOf(content);
str = APOS_PATTERN.matcher(str).replaceAll("'");
str = QUOT_PATTERN.matcher(str).replaceAll("\"");
str = LT_PATTERN.matcher(str).replaceAll("<");
str = GT_PATTERN.matcher(str).replaceAll(">");
str = AMP_PATTERN.matcher(str).replaceAll("&");
str = LCURL_PATTERN.matcher(str).replaceAll("{");
return str;
}
/**
* Construct a new (empty) DOM Document and return it.
*
* @return an empty DOM Document
*/
public static Document createDOMDocument()
{
try {
return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
} catch (ParserConfigurationException ex) {
LOGGER.error("Cannot create DOM Documents", ex);
return null;
}
}
/**
* Parse a DOM Document from a source.
*
* @param source the source input to parse
* @return the equivalent DOM Document, or {@code null} if the parsing failed.
*/
public static Document parse(LSInput source)
{
try {
LSParser p = LS_IMPL.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
// Disable validation, since this takes a lot of time and causes unneeded network traffic
p.getDomConfig().setParameter("validate", false);
if (p.getDomConfig().canSetParameter(DISABLE_DTD_PARAM, false)) {
p.getDomConfig().setParameter(DISABLE_DTD_PARAM, false);
}
// Avoid XML eXternal Entity injection (XXE)
if (p.getDomConfig().canSetParameter(DISABLE_EXTERNAL_DOCTYPE_DECLARATION, false)) {
p.getDomConfig().setParameter(DISABLE_EXTERNAL_DOCTYPE_DECLARATION, false);
}
if (p.getDomConfig().canSetParameter(DISABLE_EXTERNAL_PARAMETER_ENTITIES, false)) {
p.getDomConfig().setParameter(DISABLE_EXTERNAL_PARAMETER_ENTITIES, false);
}
if (p.getDomConfig().canSetParameter(DISABLE_EXTERNAL_GENERAL_ENTITIES, false)) {
p.getDomConfig().setParameter(DISABLE_EXTERNAL_GENERAL_ENTITIES, false);
}
return p.parse(source);
} catch (Exception ex) {
LOGGER.warn("Cannot parse XML document: [{}]", ex.getMessage());
return null;
}
}
/**
* Serialize a DOM Node into a string, including the XML declaration at the start.
*
* @param node the node to export
* @return the serialized node, or an empty string if the serialization fails
*/
public static String serialize(Node node)
{
return serialize(node, true);
}
/**
* Serialize a DOM Node into a string, with an optional XML declaration at the start.
*
* @param node the node to export
* @param withXmlDeclaration whether to output the XML declaration or not
* @return the serialized node, or an empty string if the serialization fails or the node is {@code null}
*/
public static String serialize(Node node, boolean withXmlDeclaration)
{
if (node == null) {
return "";
}
try {
LSOutput output = LS_IMPL.createLSOutput();
StringWriter result = new StringWriter();
output.setCharacterStream(result);
LSSerializer serializer = LS_IMPL.createLSSerializer();
serializer.getDomConfig().setParameter("xml-declaration", withXmlDeclaration);
serializer.setNewLine(NEWLINE);
String encoding = "UTF-8";
if (node instanceof Document) {
encoding = ((Document) node).getXmlEncoding();
} else if (node.getOwnerDocument() != null) {
encoding = node.getOwnerDocument().getXmlEncoding();
}
output.setEncoding(encoding);
serializer.write(node, output);
return result.toString();
} catch (Exception ex) {
LOGGER.warn("Failed to serialize node to XML String: [{}]", ex.getMessage());
return "";
}
}
/**
* Apply an XSLT transformation to a Document.
*
* @param xml the document to transform
* @param xslt the stylesheet to apply
* @return the transformation result, or {@code null} if an error occurs or {@code null} xml or xslt input
*/
public static String transform(Source xml, Source xslt)
{
if (xml != null && xslt != null) {
try {
StringWriter output = new StringWriter();
Result result = new StreamResult(output);
Source safeXMLSource = createSafeSource(xml);
XMLUtils.createTransformerFactory().newTransformer(xslt).transform(safeXMLSource, result);
return output.toString();
} catch (Exception ex) {
LOGGER.warn("Failed to apply XSLT transformation: [{}]", ex.getMessage());
}
}
return null;
}
/**
* Parse and pretty print a XML content.
*
* @param content the XML content to format
* @return the formatted version of the passed XML content
* @throws TransformerFactoryConfigurationError when failing to create a
* {@link TransformerFactoryConfigurationError}
* @throws TransformerException when failing to transform the content
* @since 5.2M1
*/
public static String formatXMLContent(String content) throws TransformerFactoryConfigurationError,
TransformerException
{
// TODO: Deprecate and legacify this method and introduce a new signature that doesn't throw any exception
// Or better, also change XMLUtils.parse() to throw an exception and throw that exception here. Introduce an
// XWiki exception to not be dependent of the XML parsing implementation used.
if (content == null) {
return null;
}
LSInput input = LS_IMPL.createLSInput();
input.setCharacterStream(new StringReader(content));
Format format = Format.getPrettyFormat();
format.setLineSeparator(NEWLINE);
Document document = XMLUtils.parse(input);
if (document == null) {
throw new TransformerException(String.format("Failed to pretty print XML content [%s]", content));
}
DOMBuilder builder = new DOMBuilder();
return new XMLOutputter(format).outputString(builder.build(document));
}
private static Source createSafeSource(Source originalSource) throws ParserConfigurationException, SAXException
{
Source safeSource;
if (originalSource instanceof StreamSource) {
StreamSource stream = (StreamSource) originalSource;
InputSource inputSource;
if (stream.getReader() == null) {
inputSource = new InputSource(stream.getInputStream());
} else {
inputSource = new InputSource(stream.getReader());
}
inputSource.setPublicId(stream.getPublicId());
inputSource.setSystemId(originalSource.getSystemId());
safeSource = new SAXSource(XML_READER_FACTORY.createXMLReader(), inputSource);
} else if (originalSource instanceof SAXSource) {
SAXSource originalSAXSource = (SAXSource) originalSource;
safeSource = new SAXSource(XML_READER_FACTORY.createXMLReader(), originalSAXSource.getInputSource());
safeSource.setSystemId(originalSAXSource.getSystemId());
} else {
// We don't handle this type of source by using our local resolver but it's still safe since we use the
// FEATURE_SECURE_PROCESSING feature in createTransformerFactory().
safeSource = originalSource;
}
return safeSource;
}
private static TransformerFactory createTransformerFactory() throws TransformerConfigurationException
{
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
return tf;
}
}