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