View Javadoc

1   package delight.nashornsandbox.internal;
2   
3   import com.google.common.base.Objects;
4   import delight.async.Value;
5   import delight.nashornsandbox.NashornSandbox;
6   import delight.nashornsandbox.exceptions.ScriptCPUAbuseException;
7   import delight.nashornsandbox.internal.BeautifyJs;
8   import delight.nashornsandbox.internal.InterruptTest;
9   import delight.nashornsandbox.internal.MonitorThread;
10  import delight.nashornsandbox.internal.SandboxClassFilter;
11  import java.util.HashMap;
12  import java.util.Map;
13  import java.util.Random;
14  import java.util.Set;
15  import java.util.concurrent.ExecutorService;
16  import java.util.regex.Matcher;
17  import java.util.regex.Pattern;
18  import javax.script.ScriptEngine;
19  import javax.script.ScriptException;
20  import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
21  import jdk.nashorn.api.scripting.ScriptObjectMirror;
22  import org.eclipse.xtend2.lib.StringConcatenation;
23  import org.eclipse.xtext.xbase.lib.Exceptions;
24  
25  @SuppressWarnings("all")
26  public class NashornSandboxImpl implements NashornSandbox {
27    private SandboxClassFilter sandboxClassFilter;
28    
29    private final Map<String, Object> globalVariables;
30    
31    private ScriptEngine scriptEngine;
32    
33    private Long maxCPUTimeInMs = Long.valueOf(0L);
34    
35    private ExecutorService exectuor;
36    
37    private boolean allowPrintFunctions = false;
38    
39    private boolean allowReadFunctions = false;
40    
41    private boolean allowLoadFunctions = false;
42    
43    private boolean allowExitFunctions = false;
44    
45    private boolean allowGlobalsObjects = false;
46    
47    public void assertScriptEngine() {
48      try {
49        if ((this.scriptEngine != null)) {
50          return;
51        }
52        final NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
53        this.scriptEngine = factory.getScriptEngine(this.sandboxClassFilter);
54        this.scriptEngine.eval("var window = {};");
55        this.scriptEngine.eval(BeautifyJs.CODE);
56        Set<Map.Entry<String, Object>> _entrySet = this.globalVariables.entrySet();
57        for (final Map.Entry<String, Object> entry : _entrySet) {
58          this.scriptEngine.put(entry.getKey(), entry.getValue());
59        }
60        String _xifexpression = null;
61        if ((!this.allowExitFunctions)) {
62          _xifexpression = (("" + "quit = function() {};\n") + "exit = function() {};\n");
63        } else {
64          _xifexpression = "";
65        }
66        String _plus = ("\n" + _xifexpression);
67        String _plus_1 = (_plus + "\n");
68        String _xifexpression_1 = null;
69        if ((!this.allowPrintFunctions)) {
70          _xifexpression_1 = (("" + "print = function() {};\n") + "echo = function() {};\n");
71        } else {
72          _xifexpression_1 = "";
73        }
74        String _plus_2 = (_plus_1 + _xifexpression_1);
75        String _plus_3 = (_plus_2 + "\n");
76        String _xifexpression_2 = null;
77        if ((!this.allowReadFunctions)) {
78          _xifexpression_2 = (("" + "readFully = function() {};\n") + "readLine = function() {};\n");
79        } else {
80          _xifexpression_2 = "";
81        }
82        String _plus_4 = (_plus_3 + _xifexpression_2);
83        String _plus_5 = (_plus_4 + "\n");
84        String _xifexpression_3 = null;
85        if ((!this.allowLoadFunctions)) {
86          _xifexpression_3 = (("" + "load = function() {};\n") + "loadWithNewGlobal = function() {};\n");
87        } else {
88          _xifexpression_3 = "";
89        }
90        String _plus_6 = (_plus_5 + _xifexpression_3);
91        String _plus_7 = (_plus_6 + "\n");
92        String _xifexpression_4 = null;
93        if ((!this.allowGlobalsObjects)) {
94          _xifexpression_4 = ((((((("" + "$ARG = null;\n") + "$ENV = null;\n") + "$EXEC = null;\n") + "$OPTIONS = null;\n") + 
95            "$OUT = null;\n") + "$ERR = null;\n") + "$EXIT = null;\n");
96        } else {
97          _xifexpression_4 = "";
98        }
99        String _plus_8 = (_plus_7 + _xifexpression_4);
100       String _plus_9 = (_plus_8 + "\n");
101       this.scriptEngine.eval(_plus_9);
102     } catch (Throwable _e) {
103       throw Exceptions.sneakyThrow(_e);
104     }
105   }
106   
107   private static String replaceGroup(final String str, final String regex, final String replacementForGroup2) {
108     final Pattern pattern = Pattern.compile(regex);
109     final Matcher matcher = pattern.matcher(str);
110     final StringBuffer sb = new StringBuffer();
111     while (matcher.find()) {
112       matcher.appendReplacement(sb, ("$1" + replacementForGroup2));
113     }
114     matcher.appendTail(sb);
115     return sb.toString();
116   }
117   
118   private static String injectInterruptionCalls(final String str, final int randomToken) {
119     String _xblockexpression = null;
120     {
121       String res = str.replaceAll(";\\n", ((";intCheckForInterruption" + Integer.valueOf(randomToken)) + "();\n"));
122       res = NashornSandboxImpl.replaceGroup(res, "(while \\([^\\)]*)(\\) \\{)", ((") {intCheckForInterruption" + Integer.valueOf(randomToken)) + "();\n"));
123       res = NashornSandboxImpl.replaceGroup(res, "(for \\([^\\)]*)(\\) \\{)", ((") {intCheckForInterruption" + Integer.valueOf(randomToken)) + "();\n"));
124       _xblockexpression = res = res.replaceAll("\\} while \\(", (("\nintCheckForInterruption" + Integer.valueOf(randomToken)) + "();\n\\} while \\("));
125     }
126     return _xblockexpression;
127   }
128   
129   @Override
130   public Object eval(final String js) {
131     try {
132       Object _xblockexpression = null;
133       {
134         this.assertScriptEngine();
135         if (((this.maxCPUTimeInMs).longValue() == 0)) {
136           return this.scriptEngine.eval(js);
137         }
138         Object _xsynchronizedexpression = null;
139         synchronized (this) {
140           Object _xblockexpression_1 = null;
141           {
142             final Value<Object> resVal = new Value<Object>(null);
143             final Value<Throwable> exceptionVal = new Value<Throwable>(null);
144             final MonitorThread monitorThread = new MonitorThread(((this.maxCPUTimeInMs).longValue() * 1000000));
145             if ((this.exectuor == null)) {
146               throw new IllegalStateException(
147                 "When a CPU time limit is set, an executor needs to be provided by calling .setExecutor(...)");
148             }
149             final Object monitor = new Object();
150             final Runnable _function = new Runnable() {
151               @Override
152               public void run() {
153                 try {
154                   boolean _contains = js.contains("intCheckForInterruption");
155                   if (_contains) {
156                     throw new IllegalArgumentException(
157                       "Script contains the illegal string [intCheckForInterruption]");
158                   }
159                   Object _eval = NashornSandboxImpl.this.scriptEngine.eval("window.js_beautify;");
160                   final ScriptObjectMirror jsBeautify = ((ScriptObjectMirror) _eval);
161                   Object _call = jsBeautify.call("beautify", js);
162                   final String beautifiedJs = ((String) _call);
163                   final int randomToken = Math.abs(new Random().nextInt());
164                   StringConcatenation _builder = new StringConcatenation();
165                   _builder.append("var InterruptTest = Java.type(\'");
166                   String _name = InterruptTest.class.getName();
167                   _builder.append(_name);
168                   _builder.append("\');");
169                   _builder.newLineIfNotEmpty();
170                   _builder.append("var isInterrupted = InterruptTest.isInterrupted;");
171                   _builder.newLine();
172                   _builder.append("var intCheckForInterruption");
173                   _builder.append(randomToken);
174                   _builder.append(" = function() {");
175                   _builder.newLineIfNotEmpty();
176                   _builder.append("\t");
177                   _builder.append("if (isInterrupted()) {");
178                   _builder.newLine();
179                   _builder.append("\t    ");
180                   _builder.append("throw new Error(\'Interrupted");
181                   _builder.append(randomToken, "\t    ");
182                   _builder.append("\')");
183                   _builder.newLineIfNotEmpty();
184                   _builder.append("\t");
185                   _builder.append("}");
186                   _builder.newLine();
187                   _builder.append("};");
188                   _builder.newLine();
189                   String _injectInterruptionCalls = NashornSandboxImpl.injectInterruptionCalls(beautifiedJs, randomToken);
190                   final String securedJs = (_builder.toString() + _injectInterruptionCalls);
191                   final Thread mainThread = Thread.currentThread();
192                   monitorThread.setThreadToMonitor(Thread.currentThread());
193                   final Runnable _function = new Runnable() {
194                     @Override
195                     public void run() {
196                       mainThread.interrupt();
197                     }
198                   };
199                   monitorThread.setOnInvalidHandler(_function);
200                   monitorThread.start();
201                   try {
202                     final Object res = NashornSandboxImpl.this.scriptEngine.eval(securedJs);
203                     resVal.set(res);
204                   } catch (final Throwable _t) {
205                     if (_t instanceof ScriptException) {
206                       final ScriptException e = (ScriptException)_t;
207                       boolean _contains_1 = e.getMessage().contains(("Interrupted" + Integer.valueOf(randomToken)));
208                       if (_contains_1) {
209                         monitorThread.notifyOperationInterrupted();
210                       } else {
211                         exceptionVal.set(e);
212                         monitorThread.stopMonitor();
213                         synchronized (monitor) {
214                           monitor.notify();
215                         }
216                         return;
217                       }
218                     } else {
219                       throw Exceptions.sneakyThrow(_t);
220                     }
221                   } finally {
222                     monitorThread.stopMonitor();
223                     synchronized (monitor) {
224                       monitor.notify();
225                     }
226                   }
227                 } catch (final Throwable _t_1) {
228                   if (_t_1 instanceof Throwable) {
229                     final Throwable t = (Throwable)_t_1;
230                     exceptionVal.set(t);
231                     monitorThread.stopMonitor();
232                     synchronized (monitor) {
233                       monitor.notify();
234                     }
235                   } else {
236                     throw Exceptions.sneakyThrow(_t_1);
237                   }
238                 }
239               }
240             };
241             this.exectuor.execute(_function);
242             synchronized (monitor) {
243               monitor.wait();
244             }
245             boolean _isCPULimitExceeded = monitorThread.isCPULimitExceeded();
246             if (_isCPULimitExceeded) {
247               String notGraceful = "";
248               boolean _gracefullyInterrputed = monitorThread.gracefullyInterrputed();
249               boolean _not = (!_gracefullyInterrputed);
250               if (_not) {
251                 notGraceful = " The operation could not be gracefully interrupted.";
252               }
253               Throwable _get = exceptionVal.get();
254               throw new ScriptCPUAbuseException(
255                 ((("Script used more than the allowed [" + this.maxCPUTimeInMs) + " ms] of CPU time. ") + notGraceful), _get);
256             }
257             Throwable _get_1 = exceptionVal.get();
258             boolean _notEquals = (!Objects.equal(_get_1, null));
259             if (_notEquals) {
260               throw exceptionVal.get();
261             }
262             _xblockexpression_1 = resVal.get();
263           }
264           _xsynchronizedexpression = _xblockexpression_1;
265         }
266         _xblockexpression = _xsynchronizedexpression;
267       }
268       return _xblockexpression;
269     } catch (Throwable _e) {
270       throw Exceptions.sneakyThrow(_e);
271     }
272   }
273   
274   @Override
275   public NashornSandbox setMaxCPUTime(final long limit) {
276     NashornSandboxImpl _xblockexpression = null;
277     {
278       this.maxCPUTimeInMs = Long.valueOf(limit);
279       _xblockexpression = this;
280     }
281     return _xblockexpression;
282   }
283   
284   @Override
285   public NashornSandbox allow(final Class<?> clazz) {
286     NashornSandboxImpl _xblockexpression = null;
287     {
288       this.sandboxClassFilter.add(clazz.getName());
289       _xblockexpression = this;
290     }
291     return _xblockexpression;
292   }
293   
294   @Override
295   public void disallow(final Class<?> clazz) {
296     this.sandboxClassFilter.remove(clazz.getName());
297   }
298   
299   @Override
300   public boolean isAllowed(final Class<?> clazz) {
301     return this.sandboxClassFilter.contains(clazz.getName());
302   }
303   
304   @Override
305   public void disallowAllClasses() {
306     this.sandboxClassFilter.clear();
307   }
308   
309   @Override
310   public NashornSandbox inject(final String variableName, final Object object) {
311     NashornSandboxImpl _xblockexpression = null;
312     {
313       this.globalVariables.put(variableName, object);
314       boolean _contains = this.sandboxClassFilter.contains(object.getClass().getName());
315       boolean _not = (!_contains);
316       if (_not) {
317         this.allow(object.getClass());
318       }
319       boolean _notEquals = (!Objects.equal(this.scriptEngine, null));
320       if (_notEquals) {
321         this.scriptEngine.put(variableName, object);
322       }
323       _xblockexpression = this;
324     }
325     return _xblockexpression;
326   }
327   
328   @Override
329   public NashornSandbox setExecutor(final ExecutorService executor) {
330     NashornSandboxImpl _xblockexpression = null;
331     {
332       this.exectuor = executor;
333       _xblockexpression = this;
334     }
335     return _xblockexpression;
336   }
337   
338   @Override
339   public ExecutorService getExecutor() {
340     return this.exectuor;
341   }
342   
343   @Override
344   public Object get(final String variableName) {
345     Object _xblockexpression = null;
346     {
347       this.assertScriptEngine();
348       _xblockexpression = this.scriptEngine.get(variableName);
349     }
350     return _xblockexpression;
351   }
352   
353   @Override
354   public void allowPrintFunctions(final boolean v) {
355     this.allowPrintFunctions = v;
356   }
357   
358   @Override
359   public void allowReadFunctions(final boolean v) {
360     this.allowReadFunctions = v;
361   }
362   
363   @Override
364   public void allowLoadFunctions(final boolean v) {
365     this.allowLoadFunctions = v;
366   }
367   
368   @Override
369   public void allowExitFunctions(final boolean v) {
370     this.allowExitFunctions = v;
371   }
372   
373   @Override
374   public void allowGlobalsObjects(final boolean v) {
375     this.allowGlobalsObjects = v;
376   }
377   
378   public NashornSandboxImpl() {
379     SandboxClassFilter _sandboxClassFilter = new SandboxClassFilter();
380     this.sandboxClassFilter = _sandboxClassFilter;
381     HashMap<String, Object> _hashMap = new HashMap<String, Object>();
382     this.globalVariables = _hashMap;
383     this.allow(InterruptTest.class);
384   }
385 }