我有API的Java应用程序。 API允许使用灵活的日期语法:
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?
请注意我无法明确地将输入评估为groovy代码,因为API用户可能会插入groovy代码并破解服务器。
答案 0 :(得分: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());
}
}
}