2014-06-23 83 views
2

我有帶API的Java應用程序。 API允許採用靈活的日期語法:Groovy腳本沙箱:使用來自Java的Groovy TimeCategory語法作爲字符串

PUT /monthly-jobs/job1 
{ 
    "startExpression": "now + 1.week + 4.days + 2.hours" 
} 

對於這個任務,我想使用Groovy的TimeCategory類(http://groovycookbook.org/basic_types/dates_times/)。

在我看來,應該有一個幫手的Java類爲此根據接口:

interface DateExpressionEvaluator { 
    Date evaluateDateExpression(String expr); 
} 

什麼是這個正確的做法?如何在這樣的需求中使用Groovy?

請注意我無法簡單地將輸入作爲常規代碼進行評估,因爲API用戶可能會插入常規代碼並破解服務器。

+1

使用像[prettytime-nlp](http://ocpsoft.org/prettytime/nlp/)這樣的庫可能會更簡單,它可以解析「從現在起三天」 –

回答

1

我不得不使用Groovy腳本沙箱(https://github.com/kohsuke/groovy-sandbox)創建一個解決方案:

測試用例:

import static org.junit.Assert.assertEquals; 
import static org.junit.Assert.fail; 

import java.util.Date; 

import org.apache.commons.lang3.time.FastDateFormat; 
import org.junit.Test; 

public class DateExpressionUtilsTest { 

    @Test 
    public void test_parse() { 
     assertEquals(
       FastDateFormat.getInstance("yyyy-MM-dd hh:mm").format(
         new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)), 
       FastDateFormat.getInstance("yyyy-MM-dd hh:mm").format(DateExpressionUtils.eval("now + 1.week"))); 

     try { 
      DateExpressionUtils.eval("now + 1.week; Thread.sleep(1000);"); 
      fail(); 
     } catch (SecurityException e) { 
      // ok 
     } 
    } 
} 

DateExpressionUtils:

import groovy.lang.Binding; 
import groovy.lang.GroovyShell; 

import java.util.Date; 

import org.codehaus.groovy.control.CompilerConfiguration; 
import org.kohsuke.groovy.sandbox.SandboxTransformer; 

/** 
* Sandboxing: https://github.com/kohsuke/groovy-sandbox 
* http://groovy-sandbox.kohsuke.org/ 
* 
*/ 

public class DateExpressionUtils { 
    public static Date eval(String expr) { 
     try { 
      CompilerConfiguration cc = new CompilerConfiguration(); 
      cc.addCompilationCustomizers(new SandboxTransformer()); 
      Binding binding = new Binding(); 
      binding.setProperty("now", new Date()); 
      GroovyShell sh = new GroovyShell(binding, cc); 
      DateExpressionSandbox sandbox = new DateExpressionSandbox(); 
      sandbox.register(); 
      try { 
       Object resObj = sh.evaluate("use(groovy.time.TimeCategory){" + expr + "}"); 
       Date res = (Date) resObj; 
       return res; 
      } finally { 
       sandbox.unregister(); 
      } 
     } catch (SecurityException e) { 
      throw new SecurityException(String.format("Possible date expression sandbox jailbreak with '%s': '%s'.", 
        expr, e.getMessage())); 
     } catch (Exception e) { 
      throw new RuntimeException(String.format("Unable to evaluate date expression '%s': '%s'.", expr, 
        e.getMessage())); 
     } 
    } 
} 

DateExpressionSandbox:

import groovy.lang.Closure; 
import groovy.lang.Script; 
import groovy.time.Duration; 
import groovy.time.TimeCategory; 

import java.util.Arrays; 
import java.util.Date; 
import java.util.HashSet; 
import java.util.Set; 

import org.kohsuke.groovy.sandbox.GroovyValueFilter; 

/** 
* Example sandbox: https://github.com/kohsuke/groovy-sandbox/blob/master/src/test/groovy/org/ 
* kohsuke/groovy/sandbox/robot/RobotSandbox.groovy 
* 
*/ 

public class DateExpressionSandbox extends GroovyValueFilter { 
    @Override 
    public Object filter(Object o) { 
     if (o == null || ALLOWED_TYPES.contains(o.getClass())) 
      return o; 

     if (Class.class.equals(o.getClass()) && ALLOWED_STATIC_CLASSES.contains(o.toString())) { 
      return o; 
     } 

     if (o instanceof Script || o instanceof Closure) 
      return o; // access to properties of compiled groovy script 

     throw new SecurityException(String.format("Unexpected type: '%s', '%s'.", o.getClass(), o)); 
    } 

    private static final Set<Class<?>> ALLOWED_TYPES = new HashSet<Class<?>>(Arrays.asList(String.class, Integer.class, 
      Long.class, Double.class, Boolean.class, Date.class, TimeCategory.class, Duration.class)); 

    private static final Set<String> ALLOWED_STATIC_CLASSES = new HashSet<>(); 
    static { 
     for (Class<?> cl : ALLOWED_TYPES) { 
      ALLOWED_STATIC_CLASSES.add("class " + cl.getCanonicalName()); 
     } 
    } 
}