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