package hudson.plugins.emailext.plugins.content;
import groovy.lang.Binding;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import groovy.text.SimpleTemplateEngine;
import groovy.text.Template;
import hudson.FilePath;
import hudson.model.Item;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.emailext.ExtendedEmailPublisherDescriptor;
import hudson.plugins.emailext.GroovyTemplateConfig.GroovyTemplateConfigProvider;
import hudson.plugins.emailext.groovy.sandbox.EmailExtScriptTokenMacroWhitelist;
import hudson.plugins.emailext.groovy.sandbox.PrintStreamInstanceWhitelist;
import hudson.plugins.emailext.groovy.sandbox.StaticProxyInstanceWhitelist;
import hudson.plugins.emailext.groovy.sandbox.TaskListenerInstanceWhitelist;
import hudson.plugins.emailext.plugins.EmailToken;
import jenkins.model.Jenkins;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.jenkinsci.lib.configprovider.ConfigProvider;
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.ProxyWhitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist;
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext;
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage;
import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
@EmailToken
public class ScriptContent extends AbstractEvalContent {
private static final Logger LOGGER = Logger.getLogger(ScriptContent.class.getName());
@Parameter
public String script = "";
private static final String DEFAULT_TEMPLATE_NAME = "groovy-html.template";
@Parameter
public String template = DEFAULT_TEMPLATE_NAME;
public static final String MACRO_NAME = "SCRIPT";
private static final Map<String,Reference<Template>> templateCache = new HashMap<>();
public ScriptContent() {
super(MACRO_NAME);
}
@Override
public String evaluate(Run<?, ?> run, FilePath workspace, TaskListener listener, String macroName) throws MacroEvaluationException, IOException, InterruptedException {
InputStream inputStream = null;
String result = "";
try {
if (!StringUtils.isEmpty(script)) {
inputStream = getFileInputStream(workspace, script, ".groovy");
result = executeScript(run, listener, inputStream);
} else {
inputStream = getFileInputStream(workspace, template, ".template");
result = renderTemplate(run, listener, inputStream);
}
} catch (FileNotFoundException e) {
String missingScriptError = "";
if (!StringUtils.isEmpty(script)) {
missingScriptError = generateMissingFile("Groovy Script", script);
} else {
missingScriptError = generateMissingFile("Groovy Template", template);
}
LOGGER.log(Level.SEVERE, missingScriptError);
result = missingScriptError;
} catch (GroovyRuntimeException e) {
result = "Error in script or template: " + e.toString();
} finally {
IOUtils.closeQuietly(inputStream);
}
return result;
}
@Override
protected Class<? extends ConfigProvider> getProviderClass () {
return GroovyTemplateConfigProvider.class;
}
/**
* Renders the template using a SimpleTemplateEngine
*
* @param build the build to act on
* @param templateStream the template file stream
* @return the rendered template content
* @throws IOException
*/
private String renderTemplate(Run<?, ?> build, TaskListener listener, InputStream templateStream)
throws IOException {
String result;
final Map<String, Object> binding = new HashMap<>();
ExtendedEmailPublisherDescriptor descriptor = Jenkins.getActiveInstance().getDescriptorByType(ExtendedEmailPublisherDescriptor.class);
binding.put("build", build);
binding.put("listener", listener);
binding.put("it", new ScriptContentBuildWrapper(build));
binding.put("rooturl", descriptor.getHudsonUrl());
binding.put("project", build.getParent());
try {
String text = IOUtils.toString(templateStream);
boolean approvedScript = false;
if (templateStream instanceof UserProvidedContentInputStream && !AbstractEvalContent.isApprovedScript(text, GroovyLanguage.get())) {
approvedScript = false;
ScriptApproval.get().configuring(text, GroovyLanguage.get(), ApprovalContext.create().withItem(build.getParent()));
} else {
approvedScript = true;
}
// we add the binding to the SimpleTemplateEngine instead of the shell
GroovyShell shell = createEngine(descriptor, Collections.<String, Object>emptyMap(), !approvedScript);
SimpleTemplateEngine engine = new SimpleTemplateEngine(shell);
Template tmpl;
synchronized (templateCache) {
Reference<Template> templateR = templateCache.get(text);
tmpl = templateR == null ? null : templateR.get();
if (tmpl == null) {
tmpl = engine.createTemplate(text);
templateCache.put(text, new SoftReference<>(tmpl));
}
}
final Template tmplR = tmpl;
if (approvedScript) {
//The script has been approved by an admin, so run it as is
result = tmplR.make(binding).toString();
} else {
//unapproved script, so run in sandbox
StaticProxyInstanceWhitelist whitelist = new StaticProxyInstanceWhitelist(build, "templates-instances.whitelist");
result = GroovySandbox.runInSandbox(new Callable<String>() {
@Override
public String call() throws Exception {
return tmplR.make(binding).toString(); //TODO there is a PrintWriter instance created in make and bound to out
}
}, new ProxyWhitelist(
Whitelist.all(),
new TaskListenerInstanceWhitelist(listener),
new PrintStreamInstanceWhitelist(listener.getLogger()),
new EmailExtScriptTokenMacroWhitelist(),
whitelist));
}
} catch(Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
result = "Exception raised during template rendering: " + e.getMessage() + "\n\n" + sw.toString();
}
return result;
}
/**
* Executes a script and returns the last value as a String
*
* @param build the build to act on
* @param scriptStream the script input stream
* @return a String containing the toString of the last item in the script
* @throws IOException
*/
private String executeScript(Run<?, ?> build, TaskListener listener, InputStream scriptStream)
throws IOException {
String result = "";
Map binding = new HashMap<>();
ExtendedEmailPublisherDescriptor descriptor = Jenkins.getActiveInstance().getDescriptorByType(ExtendedEmailPublisherDescriptor.class);
Item parent = build.getParent();
binding.put("build", build);
binding.put("it", new ScriptContentBuildWrapper(build));
binding.put("project", parent);
binding.put("rooturl", descriptor.getHudsonUrl());
PrintStream logger = listener.getLogger();
binding.put("logger", logger);
String scriptContent = IOUtils.toString(scriptStream, descriptor.getCharset());
if (scriptStream instanceof UserProvidedContentInputStream) {
ScriptApproval.get().configuring(scriptContent, GroovyLanguage.get(), ApprovalContext.create().withItem(parent));
}
if (scriptStream instanceof UserProvidedContentInputStream && !AbstractEvalContent.isApprovedScript(scriptContent, GroovyLanguage.get())) {
//Unapproved script, run it in the sandbox
GroovyShell shell = createEngine(descriptor, binding, true);
Script script = shell.parse(scriptContent);
Object res = GroovySandbox.run(script, new ProxyWhitelist(
Whitelist.all(),
new PrintStreamInstanceWhitelist(logger),
new EmailExtScriptTokenMacroWhitelist()
));
if (res != null) {
result = res.toString();
}
} else {
if (scriptStream instanceof UserProvidedContentInputStream) {
ScriptApproval.get().using(scriptContent, GroovyLanguage.get());
}
//Pre approved script, so run as is
GroovyShell shell = createEngine(descriptor, binding, false);
Script script = shell.parse(scriptContent);
Object res = script.run();
if (res != null) {
result = res.toString();
}
}
return result;
}
/**
* Creates an engine (GroovyShell) to be used to execute Groovy code
*
* @param variables user variables to be added to the Groovy context
* @return a GroovyShell instance
* @throws FileNotFoundException
* @throws IOException
*/
private GroovyShell createEngine(ExtendedEmailPublisherDescriptor descriptor, Map<String, Object> variables, boolean secure)
throws IOException {
ClassLoader cl;
CompilerConfiguration cc;
if (secure) {
cl = GroovySandbox.createSecureClassLoader(Jenkins.getActiveInstance().getPluginManager().uberClassLoader);
cc = GroovySandbox.createSecureCompilerConfiguration();
} else {
cl = Jenkins.getActiveInstance().getPluginManager().uberClassLoader;
cc = new CompilerConfiguration();
}
cc.setScriptBaseClass(EmailExtScript.class.getCanonicalName());
cc.addCompilationCustomizers(new ImportCustomizer().addStarImports(
"jenkins",
"jenkins.model",
"hudson",
"hudson.model"));
Binding binding = new Binding();
for (Map.Entry<String, Object> e : variables.entrySet()) {
binding.setVariable(e.getKey(), e.getValue());
}
return new GroovyShell(cl, binding, cc);
}
@Override
public boolean hasNestedContent() {
return false;
}
}