package delight.nashornsandbox.internal; import delight.async.Value; import delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.exceptions.ScriptCPUAbuseException; import delight.nashornsandbox.internal.BeautifyJs; import delight.nashornsandbox.internal.InterruptTest; import delight.nashornsandbox.internal.MonitorThread; import delight.nashornsandbox.internal.SandboxClassFilter; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.script.ScriptEngine; import javax.script.ScriptException; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import jdk.nashorn.api.scripting.ScriptObjectMirror; import org.eclipse.xtend2.lib.StringConcatenation; import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.InputOutput; @SuppressWarnings("all") public class NashornSandboxImpl implements NashornSandbox { protected SandboxClassFilter sandboxClassFilter; protected final Map<String, Object> globalVariables; protected ScriptEngine scriptEngine; protected Long maxCPUTimeInMs = Long.valueOf(0L); protected ExecutorService exectuor; protected boolean allowPrintFunctions = false; protected boolean allowReadFunctions = false; protected boolean allowLoadFunctions = false; protected boolean allowExitFunctions = false; protected boolean allowGlobalsObjects = false; protected volatile boolean debug = false; public void assertScriptEngine() { try { if ((this.scriptEngine != null)) { return; } final NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); ScriptEngine _scriptEngine = factory.getScriptEngine(this.sandboxClassFilter); this.scriptEngine = _scriptEngine; this.scriptEngine.eval("var window = {};"); this.scriptEngine.eval(BeautifyJs.CODE); Set<Map.Entry<String, Object>> _entrySet = this.globalVariables.entrySet(); for (final Map.Entry<String, Object> entry : _entrySet) { String _key = entry.getKey(); Object _value = entry.getValue(); this.scriptEngine.put(_key, _value); } String _xifexpression = null; if ((!this.allowExitFunctions)) { _xifexpression = (("" + "quit = function() {};\n") + "exit = function() {};\n"); } else { _xifexpression = ""; } String _plus = ("\n" + _xifexpression); String _plus_1 = (_plus + "\n"); String _xifexpression_1 = null; if ((!this.allowPrintFunctions)) { _xifexpression_1 = (("" + "print = function() {};\n") + "echo = function() {};\n"); } else { _xifexpression_1 = ""; } String _plus_2 = (_plus_1 + _xifexpression_1); String _plus_3 = (_plus_2 + "\n"); String _xifexpression_2 = null; if ((!this.allowReadFunctions)) { _xifexpression_2 = (("" + "readFully = function() {};\n") + "readLine = function() {};\n"); } else { _xifexpression_2 = ""; } String _plus_4 = (_plus_3 + _xifexpression_2); String _plus_5 = (_plus_4 + "\n"); String _xifexpression_3 = null; if ((!this.allowLoadFunctions)) { _xifexpression_3 = (("" + "load = function() {};\n") + "loadWithNewGlobal = function() {};\n"); } else { _xifexpression_3 = ""; } String _plus_6 = (_plus_5 + _xifexpression_3); String _plus_7 = (_plus_6 + "\n"); String _xifexpression_4 = null; if ((!this.allowGlobalsObjects)) { _xifexpression_4 = ((((((("" + "$ARG = null;\n") + "$ENV = null;\n") + "$EXEC = null;\n") + "$OPTIONS = null;\n") + "$OUT = null;\n") + "$ERR = null;\n") + "$EXIT = null;\n"); } else { _xifexpression_4 = ""; } String _plus_8 = (_plus_7 + _xifexpression_4); String _plus_9 = (_plus_8 + "\n"); this.scriptEngine.eval(_plus_9); } catch (Throwable _e) { throw Exceptions.sneakyThrow(_e); } } protected static String replaceGroup(final String str, final String regex, final String replacementForGroup2) { final Pattern pattern = Pattern.compile(regex); final Matcher matcher = pattern.matcher(str); final StringBuffer sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, ("$1" + replacementForGroup2)); } matcher.appendTail(sb); return sb.toString(); } protected static String injectInterruptionCalls(final String str, final int randomToken) { String _xblockexpression = null; { String res = str.replaceAll(";\\n(?![\\s]*else[\\s]+)", ((";intCheckForInterruption" + Integer.valueOf(randomToken)) + "();\n")); String _replaceGroup = NashornSandboxImpl.replaceGroup(res, "(while \\([^\\)]*)(\\) \\{)", ((") {intCheckForInterruption" + Integer.valueOf(randomToken)) + "();")); res = _replaceGroup; String _replaceGroup_1 = NashornSandboxImpl.replaceGroup(res, "(for \\([^\\)]*)(\\) \\{)", ((") {intCheckForInterruption" + Integer.valueOf(randomToken)) + "();")); res = _replaceGroup_1; String _replaceAll = res.replaceAll("\\} while \\(", (("\nintCheckForInterruption" + Integer.valueOf(randomToken)) + "();\n\\} while \\(")); _xblockexpression = res = _replaceAll; } return _xblockexpression; } @Override public Object eval(final String js) { try { Object _xblockexpression = null; { this.assertScriptEngine(); if (((this.maxCPUTimeInMs).longValue() == 0)) { return this.scriptEngine.eval(js); } Object _xsynchronizedexpression = null; synchronized (this) { Object _xblockexpression_1 = null; { final Value<Object> resVal = new Value<Object>(null); final Value<Throwable> exceptionVal = new Value<Throwable>(null); final MonitorThread monitorThread = new MonitorThread(((this.maxCPUTimeInMs).longValue() * 1000000)); if ((this.exectuor == null)) { throw new IllegalStateException( "When a CPU time limit is set, an executor needs to be provided by calling .setExecutor(...)"); } final Object monitor = new Object(); final Runnable _function = new Runnable() { @Override public void run() { try { boolean _contains = js.contains("intCheckForInterruption"); if (_contains) { throw new IllegalArgumentException( "Script contains the illegal string [intCheckForInterruption]"); } Object _eval = NashornSandboxImpl.this.scriptEngine.eval("window.js_beautify;"); final ScriptObjectMirror jsBeautify = ((ScriptObjectMirror) _eval); Object _call = jsBeautify.call("beautify", js); final String beautifiedJs = ((String) _call); Random _random = new Random(); int _nextInt = _random.nextInt(); final int randomToken = Math.abs(_nextInt); StringConcatenation _builder = new StringConcatenation(); _builder.append("var InterruptTest = Java.type(\'"); String _name = InterruptTest.class.getName(); _builder.append(_name, ""); _builder.append("\');"); _builder.newLineIfNotEmpty(); _builder.append("var isInterrupted = InterruptTest.isInterrupted;"); _builder.newLine(); _builder.append("var intCheckForInterruption"); _builder.append(randomToken, ""); _builder.append(" = function() {"); _builder.newLineIfNotEmpty(); _builder.append("\t"); _builder.append("if (isInterrupted()) {"); _builder.newLine(); _builder.append("\t "); _builder.append("throw new Error(\'Interrupted"); _builder.append(randomToken, "\t "); _builder.append("\')"); _builder.newLineIfNotEmpty(); _builder.append("\t"); _builder.append("}"); _builder.newLine(); _builder.append("};"); _builder.newLine(); String preamble = _builder.toString(); String _replace = preamble.replace("\n", ""); preamble = _replace; String _injectInterruptionCalls = NashornSandboxImpl.injectInterruptionCalls(beautifiedJs, randomToken); final String securedJs = (preamble + _injectInterruptionCalls); final Thread mainThread = Thread.currentThread(); Thread _currentThread = Thread.currentThread(); monitorThread.setThreadToMonitor(_currentThread); final Runnable _function = new Runnable() { @Override public void run() { mainThread.interrupt(); } }; monitorThread.setOnInvalidHandler(_function); monitorThread.start(); try { if (NashornSandboxImpl.this.debug) { InputOutput.<String>println("--- Running JS ---"); InputOutput.<String>println(securedJs); InputOutput.<String>println("--- JS END ---"); } final Object res = NashornSandboxImpl.this.scriptEngine.eval(securedJs); resVal.set(res); } catch (final Throwable _t) { if (_t instanceof ScriptException) { final ScriptException e = (ScriptException)_t; String _message = e.getMessage(); boolean _contains_1 = _message.contains(("Interrupted" + Integer.valueOf(randomToken))); if (_contains_1) { monitorThread.notifyOperationInterrupted(); } else { exceptionVal.set(e); monitorThread.stopMonitor(); synchronized (monitor) { monitor.notify(); } return; } } else { throw Exceptions.sneakyThrow(_t); } } finally { monitorThread.stopMonitor(); synchronized (monitor) { monitor.notify(); } } } catch (final Throwable _t_1) { if (_t_1 instanceof Throwable) { final Throwable t = (Throwable)_t_1; exceptionVal.set(t); monitorThread.stopMonitor(); synchronized (monitor) { monitor.notify(); } } else { throw Exceptions.sneakyThrow(_t_1); } } } }; this.exectuor.execute(_function); synchronized (monitor) { monitor.wait(); } boolean _isCPULimitExceeded = monitorThread.isCPULimitExceeded(); if (_isCPULimitExceeded) { String notGraceful = ""; boolean _gracefullyInterrputed = monitorThread.gracefullyInterrputed(); boolean _not = (!_gracefullyInterrputed); if (_not) { notGraceful = " The operation could not be gracefully interrupted."; } Throwable _get = exceptionVal.get(); throw new ScriptCPUAbuseException( ((("Script used more than the allowed [" + this.maxCPUTimeInMs) + " ms] of CPU time. ") + notGraceful), _get); } Throwable _get_1 = exceptionVal.get(); boolean _tripleNotEquals = (_get_1 != null); if (_tripleNotEquals) { throw exceptionVal.get(); } _xblockexpression_1 = resVal.get(); } _xsynchronizedexpression = _xblockexpression_1; } _xblockexpression = _xsynchronizedexpression; } return _xblockexpression; } catch (Throwable _e) { throw Exceptions.sneakyThrow(_e); } } @Override public NashornSandbox setMaxCPUTime(final long limit) { NashornSandboxImpl _xblockexpression = null; { this.maxCPUTimeInMs = Long.valueOf(limit); _xblockexpression = this; } return _xblockexpression; } @Override public NashornSandbox allow(final Class<?> clazz) { NashornSandboxImpl _xblockexpression = null; { String _name = clazz.getName(); this.sandboxClassFilter.add(_name); _xblockexpression = this; } return _xblockexpression; } @Override public void disallow(final Class<?> clazz) { String _name = clazz.getName(); this.sandboxClassFilter.remove(_name); } @Override public boolean isAllowed(final Class<?> clazz) { String _name = clazz.getName(); return this.sandboxClassFilter.contains(_name); } @Override public void disallowAllClasses() { this.sandboxClassFilter.clear(); } @Override public NashornSandbox inject(final String variableName, final Object object) { NashornSandboxImpl _xblockexpression = null; { this.globalVariables.put(variableName, object); Class<?> _class = object.getClass(); String _name = _class.getName(); boolean _contains = this.sandboxClassFilter.contains(_name); boolean _not = (!_contains); if (_not) { Class<?> _class_1 = object.getClass(); this.allow(_class_1); } if ((this.scriptEngine != null)) { this.scriptEngine.put(variableName, object); } _xblockexpression = this; } return _xblockexpression; } @Override public NashornSandbox setExecutor(final ExecutorService executor) { NashornSandboxImpl _xblockexpression = null; { this.exectuor = executor; _xblockexpression = this; } return _xblockexpression; } @Override public ExecutorService getExecutor() { return this.exectuor; } @Override public Object get(final String variableName) { Object _xblockexpression = null; { this.assertScriptEngine(); _xblockexpression = this.scriptEngine.get(variableName); } return _xblockexpression; } @Override public void allowPrintFunctions(final boolean v) { this.allowPrintFunctions = v; } @Override public void allowReadFunctions(final boolean v) { this.allowReadFunctions = v; } @Override public void allowLoadFunctions(final boolean v) { this.allowLoadFunctions = v; } @Override public void allowExitFunctions(final boolean v) { this.allowExitFunctions = v; } @Override public void allowGlobalsObjects(final boolean v) { this.allowGlobalsObjects = v; } @Override public void setDebug(final boolean value) { this.debug = value; } public NashornSandboxImpl() { SandboxClassFilter _sandboxClassFilter = new SandboxClassFilter(); this.sandboxClassFilter = _sandboxClassFilter; HashMap<String, Object> _hashMap = new HashMap<String, Object>(); this.globalVariables = _hashMap; this.allow(InterruptTest.class); } }