mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
39828: Merged V4.1 to V4.1-BUG-FIX 39827: Merged PATCHES/V4.0.2 to V4.1 39825: ALF-13453 / ALF-13844: Merged V3.4-BUG-FIX to PATCHES/V4.0.2 39823: ALF-13552, ALF-13978: Reverse merged the following revisions - won't fix due to regressions and not a serious vulnerability 35341: ALF-13552: Merged V4.0 to V3.4 35296: ALF-13453: Merged V4.0-BUG-FIX to V4.0 35295: Fix for ALF-13453: Remote Code Execution (can create reverse shell) 35304: ALF-13453: Extra fix to ensure xalan namespace isn't declared with global scope and can't be hijacked by an input stylesheet 35307: ALF-13453: Duplicated extra fix to duplicate code in XSLTRenderingEngine! 36101: ALF-13978: Merged V4.0-BUG-FIX to V3.4 36014: ALF-13844: XSLT Filtering Not 100% Secure - added more namespaces to the security filter. - verified that include/import uses the security filter. 36108: ALF-13978: Fixed compilation errors 39824: ALF-13552, ALF-13978: Fixed compilation errors git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@39829 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
418 lines
16 KiB
Java
418 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.template;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.Reader;
|
|
import java.io.StringReader;
|
|
import java.io.Writer;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import javax.xml.transform.ErrorListener;
|
|
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.URIResolver;
|
|
import javax.xml.transform.dom.DOMSource;
|
|
import javax.xml.transform.stream.StreamResult;
|
|
|
|
import org.alfresco.repo.processor.BaseProcessor;
|
|
import org.alfresco.service.cmr.repository.TemplateException;
|
|
import org.alfresco.service.cmr.repository.TemplateProcessor;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.XMLUtil;
|
|
import org.apache.bsf.BSFManager;
|
|
import org.apache.commons.lang.StringUtils;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.apache.xml.utils.Constants;
|
|
import org.w3c.dom.Document;
|
|
import org.w3c.dom.Element;
|
|
import org.xml.sax.SAXException;
|
|
|
|
import freemarker.cache.TemplateLoader;
|
|
|
|
public class XSLTProcessor extends BaseProcessor implements TemplateProcessor
|
|
{
|
|
private static final Log log = LogFactory.getLog(XSLTProcessor.class);
|
|
|
|
private static final String MSG_UNABLE_TO_READ_TEMPLATE = "template.xslt.read_error";
|
|
private static final String MSG_UNABLE_TO_PARSE_TEMPLATE = "template.xslt.parse_error";
|
|
|
|
public final static QName ROOT_NAMESPACE = QName.createQName(null, "root_namespace");
|
|
|
|
private String defaultEncoding = "UTF-8";
|
|
private TemplateLoader templateLoader;
|
|
|
|
public void register()
|
|
{
|
|
super.register();
|
|
templateLoader = new ClassPathRepoTemplateLoader(this.services.getNodeService(), this.services
|
|
.getContentService(), defaultEncoding);
|
|
}
|
|
|
|
public void process(String template, Object model, Writer out)
|
|
{
|
|
TemplateSource templateSource;
|
|
try
|
|
{
|
|
templateSource = (TemplateSource) templateLoader.findTemplateSource(template);
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
throw new TemplateException(MSG_UNABLE_TO_READ_TEMPLATE, new Object[] { ex.getMessage() }, ex);
|
|
}
|
|
process(templateSource, model, out);
|
|
}
|
|
|
|
public void processString(final String template, Object model, Writer out)
|
|
{
|
|
TemplateSource stringTemplateSource = new TemplateSource()
|
|
{
|
|
public long lastModified()
|
|
{
|
|
return System.currentTimeMillis();
|
|
}
|
|
|
|
public InputStream getResource(String name)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public Reader getReader(String encoding) throws IOException
|
|
{
|
|
return new StringReader(template);
|
|
}
|
|
|
|
public void close() throws IOException
|
|
{
|
|
}
|
|
};
|
|
process(stringTemplateSource, model, out);
|
|
}
|
|
|
|
/**
|
|
* @param templateSource
|
|
* @param xsltModel
|
|
* @param out
|
|
*/
|
|
private void process(TemplateSource templateSource, Object model, Writer out)
|
|
{
|
|
if ((model == null) || !XSLTemplateModel.class.isAssignableFrom(model.getClass()))
|
|
{
|
|
throw new IllegalArgumentException("\"model\" must be an XSLTemplateModel object: " + model);
|
|
}
|
|
|
|
XSLTemplateModel xsltModel = (XSLTemplateModel) model;
|
|
System.setProperty("org.apache.xalan.extensions.bsf.BSFManager", BSFManager.class.getName());
|
|
|
|
Document xslTemplate;
|
|
try
|
|
{
|
|
xslTemplate = XMLUtil.parse(templateSource.getReader(defaultEncoding));
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
throw new TemplateException(MSG_UNABLE_TO_READ_TEMPLATE, new Object[] { ex.getMessage() }, ex);
|
|
}
|
|
catch (SAXException sax)
|
|
{
|
|
throw new TemplateException(MSG_UNABLE_TO_PARSE_TEMPLATE, new Object[] { sax.getMessage() }, sax);
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
templateSource.close();
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
// There's little to be done here. Log it and carry on
|
|
log.warn("Error while trying to close template stream", ex);
|
|
}
|
|
}
|
|
|
|
List<String> scriptIds = addScripts(xsltModel, xslTemplate);
|
|
addParameters(xsltModel, xslTemplate);
|
|
|
|
final LinkedList<TransformerException> errors = new LinkedList<TransformerException>();
|
|
final ErrorListener errorListener = new ErrorListener()
|
|
{
|
|
public void error(final TransformerException te) throws TransformerException
|
|
{
|
|
log.debug("error " + te.getMessageAndLocation());
|
|
errors.add(te);
|
|
}
|
|
|
|
public void fatalError(final TransformerException te) throws TransformerException
|
|
{
|
|
log.debug("fatalError " + te.getMessageAndLocation());
|
|
throw te;
|
|
}
|
|
|
|
public void warning(final TransformerException te) throws TransformerException
|
|
{
|
|
log.debug("warning " + te.getMessageAndLocation());
|
|
errors.add(te);
|
|
}
|
|
};
|
|
|
|
final TemplateSource resourceSource = templateSource;
|
|
final URIResolver uriResolver = new URIResolver()
|
|
{
|
|
public Source resolve(final String href, String base) throws TransformerException
|
|
{
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("request to resolve href " + href + " using base " + base);
|
|
}
|
|
InputStream in = null;
|
|
try
|
|
{
|
|
in = resourceSource.getResource(href);
|
|
if (in == null)
|
|
{
|
|
throw new TransformerException("unable to resolve href " + href);
|
|
}
|
|
|
|
Document d = XMLUtil.parse(in);
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("loaded " + XMLUtil.toString(d));
|
|
}
|
|
return new DOMSource(d);
|
|
}
|
|
catch (TransformerException ex)
|
|
{
|
|
throw ex;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new TransformerException("unable to load " + href, e);
|
|
}
|
|
}
|
|
};
|
|
|
|
Source xmlSource = this.getXMLSource(xsltModel);
|
|
|
|
Transformer t = null;
|
|
try
|
|
{
|
|
final TransformerFactory tf = TransformerFactory.newInstance();
|
|
tf.setErrorListener(errorListener);
|
|
tf.setURIResolver(uriResolver);
|
|
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("xslTemplate: \n" + XMLUtil.toString(xslTemplate));
|
|
}
|
|
|
|
t = tf.newTransformer(new DOMSource(xslTemplate));
|
|
|
|
if (errors.size() != 0)
|
|
{
|
|
final StringBuilder msg = new StringBuilder("errors encountered creating tranformer ... \n");
|
|
for (TransformerException te : errors)
|
|
{
|
|
msg.append(te.getMessageAndLocation()).append("\n");
|
|
}
|
|
throw new TemplateException(msg.toString());
|
|
}
|
|
|
|
t.setErrorListener(errorListener);
|
|
t.setURIResolver(uriResolver);
|
|
t.setParameter("versionParam", "2.0");
|
|
}
|
|
catch (TransformerConfigurationException tce)
|
|
{
|
|
log.error(tce);
|
|
throw new TemplateException(tce.getMessage(), tce);
|
|
}
|
|
|
|
try
|
|
{
|
|
t.transform(xmlSource, new StreamResult(out));
|
|
}
|
|
catch (TransformerException te)
|
|
{
|
|
log.error(te.getMessageAndLocation());
|
|
throw new TemplateException(te.getMessageAndLocation(), te);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.error("unexpected error " + e);
|
|
throw new TemplateException(e.getMessage(), e);
|
|
}
|
|
finally
|
|
{
|
|
//Clear out any scripts that were created for this transform
|
|
if (!scriptIds.isEmpty())
|
|
{
|
|
XSLTProcessorMethodInvoker.removeMethods(scriptIds);
|
|
}
|
|
}
|
|
|
|
if (errors.size() != 0)
|
|
{
|
|
final StringBuilder msg = new StringBuilder("errors encountered during transformation ... \n");
|
|
for (TransformerException te : errors)
|
|
{
|
|
msg.append(te.getMessageAndLocation()).append("\n");
|
|
}
|
|
throw new TemplateException(msg.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a script element to the xsl which makes static methods on this object available to the xsl tempalte.
|
|
*
|
|
* @param xslTemplate
|
|
* the xsl template
|
|
*/
|
|
protected List<String> addScripts(final XSLTemplateModel xsltModel, final Document xslTemplate)
|
|
{
|
|
final Map<QName, List<Map.Entry<QName, Object>>> methods = new HashMap<QName, List<Map.Entry<QName, Object>>>();
|
|
for (final Map.Entry<QName, Object> entry : xsltModel.entrySet())
|
|
{
|
|
if (entry.getValue() instanceof TemplateProcessorMethod)
|
|
{
|
|
final String prefix = QName.splitPrefixedQName(entry.getKey().toPrefixString())[0];
|
|
final QName qn = QName.createQName(entry.getKey().getNamespaceURI(), prefix);
|
|
if (!methods.containsKey(qn))
|
|
{
|
|
methods.put(qn, new LinkedList<Map.Entry<QName, Object>>());
|
|
}
|
|
methods.get(qn).add(entry);
|
|
}
|
|
}
|
|
|
|
final Element docEl = xslTemplate.getDocumentElement();
|
|
final String XALAN_NS = Constants.S_BUILTIN_EXTENSIONS_URL;
|
|
final String XALAN_NS_PREFIX = "xalan";
|
|
docEl.setAttribute("xmlns:" + XALAN_NS_PREFIX, XALAN_NS);
|
|
|
|
final Set<String> excludePrefixes = new HashSet<String>();
|
|
if (docEl.hasAttribute("exclude-result-prefixes"))
|
|
{
|
|
excludePrefixes.addAll(Arrays.asList(docEl.getAttribute("exclude-result-prefixes").split(" ")));
|
|
}
|
|
excludePrefixes.add(XALAN_NS_PREFIX);
|
|
|
|
final List<String> result = new LinkedList<String>();
|
|
for (QName ns : methods.keySet())
|
|
{
|
|
final String prefix = ns.getLocalName();
|
|
docEl.setAttribute("xmlns:" + prefix, ns.getNamespaceURI());
|
|
excludePrefixes.add(prefix);
|
|
|
|
final Element compEl = xslTemplate.createElementNS(XALAN_NS, XALAN_NS_PREFIX + ":component");
|
|
compEl.setAttribute("prefix", prefix);
|
|
docEl.appendChild(compEl);
|
|
String functionNames = null;
|
|
final Element scriptEl = xslTemplate.createElementNS(XALAN_NS, XALAN_NS_PREFIX + ":script");
|
|
scriptEl.setAttribute("lang", "javascript");
|
|
final StringBuilder js = new StringBuilder("var _xsltp_invoke = java.lang.Class.forName('"
|
|
+ XSLTProcessorMethodInvoker.class.getName() + "').newInstance();\n"
|
|
+ "function _xsltp_to_java_array(js_array) {\n"
|
|
+ "var java_array = java.lang.reflect.Array.newInstance(java.lang.Object, js_array.length);\n"
|
|
+ "for (var i = 0; i < js_array.length; i++) { java_array[i] = js_array[i]; }\n"
|
|
+ "return java_array; }\n");
|
|
for (final Map.Entry<QName, Object> entry : methods.get(ns))
|
|
{
|
|
if (functionNames == null)
|
|
{
|
|
functionNames = entry.getKey().getLocalName();
|
|
}
|
|
else
|
|
{
|
|
functionNames += " " + entry.getKey().getLocalName();
|
|
}
|
|
final String id = entry.getKey().getLocalName() + entry.getValue().hashCode();
|
|
js.append("function " + entry.getKey().getLocalName() + "() { return _xsltp_invoke.invokeMethod('" + id
|
|
+ "', _xsltp_to_java_array(arguments)); }\n");
|
|
XSLTProcessorMethodInvoker.addMethod(id, (TemplateProcessorMethod) entry.getValue());
|
|
result.add(id);
|
|
}
|
|
log.debug("generated JavaScript bindings:\n" + js);
|
|
scriptEl.appendChild(xslTemplate.createTextNode(js.toString()));
|
|
compEl.setAttribute("functions", functionNames);
|
|
compEl.appendChild(scriptEl);
|
|
}
|
|
docEl.setAttribute("exclude-result-prefixes", StringUtils.join(excludePrefixes
|
|
.toArray(new String[excludePrefixes.size()]), " "));
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Adds the specified parameters to the xsl template as variables within the alfresco namespace.
|
|
*
|
|
* @param xsltModel
|
|
* the variables to place within the xsl template
|
|
* @param xslTemplate
|
|
* the xsl template
|
|
*/
|
|
protected void addParameters(final XSLTemplateModel xsltModel, final Document xslTemplate)
|
|
{
|
|
final Element docEl = xslTemplate.getDocumentElement();
|
|
final String XSL_NS = docEl.getNamespaceURI();
|
|
final String XSL_NS_PREFIX = docEl.getPrefix();
|
|
|
|
for (Map.Entry<QName, Object> e : xsltModel.entrySet())
|
|
{
|
|
if (ROOT_NAMESPACE.equals(e.getKey()))
|
|
{
|
|
continue;
|
|
}
|
|
final Element el = xslTemplate.createElementNS(XSL_NS, XSL_NS_PREFIX + ":variable");
|
|
el.setAttribute("name", e.getKey().toPrefixString());
|
|
final Object o = e.getValue();
|
|
if (o instanceof String || o instanceof Number || o instanceof Boolean)
|
|
{
|
|
el.appendChild(xslTemplate.createTextNode(o.toString()));
|
|
docEl.insertBefore(el, docEl.getFirstChild());
|
|
}
|
|
}
|
|
}
|
|
|
|
protected Source getXMLSource(final Map<QName, Object> model)
|
|
{
|
|
if (!model.containsKey(ROOT_NAMESPACE))
|
|
{
|
|
return null;
|
|
}
|
|
final Object o = model.get(ROOT_NAMESPACE);
|
|
if (!(o instanceof Document))
|
|
{
|
|
throw new IllegalArgumentException("expected root namespace object to be a " + Document.class.getName()
|
|
+ ". found a " + o.getClass().getName());
|
|
}
|
|
return new DOMSource((Document) o);
|
|
}
|
|
|
|
}
|