Песочница Groovy script: используйте синтаксис Groovy TimeCategory из Java в виде строки

#java #string #grails #groovy #dsl

#java #строка #grails #groovy #dsl

Вопрос:

У меня есть Java-приложение с API. 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 code, так как пользователь API может вставить groovy code и взломать сервер.

Комментарии:

1. Возможно, было бы проще использовать библиотеку, подобную prettytime-nlp , которая может анализировать такие вещи, как «через три дня»

Ответ №1:

Мне пришлось создать решение, используя groovy script sandboxing (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()) amp;amp; 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());
        }
    }
}