3.16. 定制模板引擎

Beetl在线体验(http://ibeetl.com/beetlonline/)面临一个挑战,允许用户输入任何脚本做练习或者分享代码。但又需要防止用户输入恶意的代码,如

  1. <%
  2. for(var i=0;i<10000000;i++){
  3. //其他代码
  4. }
  5. %>

此时,需要定制模板引擎,遇到for循环的时候,应该限制循环次数,譬如,在线体验限制最多循环5次,这是通过定义替换GeneralForStatement类来完成的,这个类对应了for(exp;exp;exp) ,我们需要改成如下样子:

  1. class RestrictForStatement extends GeneralForStatement{
  2. public RestrictForStatement(GeneralForStatement gf){
  3. super(gf.varAssignSeq, gf.expInit, gf.condtion, gf.expUpdate, gf.forPart, gf.elseforPart, gf.token);
  4. }
  5. public void execute(Context ctx){
  6. if (expInit != null){
  7. for (Expression exp : expInit){
  8. exp.evaluate(ctx);
  9. }
  10. }
  11. if (varAssignSeq != null){
  12. varAssignSeq.execute(ctx);
  13. }
  14. boolean hasLooped = false;
  15. int i = 0;
  16. for (; i < 5; i++){
  17. boolean bool = (Boolean) condtion.evaluate(ctx);
  18. if (bool){
  19. hasLooped = true;
  20. forPart.execute(ctx);
  21. switch (ctx.gotoFlag){
  22. case IGoto.NORMAL:
  23. break;
  24. case IGoto.CONTINUE:
  25. ctx.gotoFlag = IGoto.NORMAL;
  26. continue;
  27. case IGoto.RETURN:
  28. return;
  29. case IGoto.BREAK:
  30. ctx.gotoFlag = IGoto.NORMAL;
  31. return;
  32. }
  33. }else{
  34. break;
  35. }
  36. if (this.expUpdate != null){
  37. for (Expression exp : expUpdate){
  38. exp.evaluate(ctx);
  39. }
  40. }
  41. }
  42. if (i >= 5){
  43. try{
  44. ctx.byteWriter.writeString("--Too may Data in loop,Ignore the left Data for Online Engine--");
  45. ctx.byteWriter.flush();
  46. } catch (IOException e){
  47. // TODO Auto-generated catch block
  48. e.printStackTrace();
  49. }
  50. }
  51. }
  52. @Override
  53. public void infer(InferContext inferCtx){
  54. super.infer(inferCtx);
  55. }
  56. }

尽管上面代码很复杂,但实际上是改写了原来的GeneralForStatement,将原来的24行while(true) 替换成for (; i < 5; i++) 用来控制最大循环,并且62行检测如果循环退出后,i等于5,则提示Too Many Data in Loop.

现在需要将此类替换原有的GeneralForStatement,

  1. public class OnlineTemplateEngine extends DefaultTemplateEngine{
  2. public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr,GroupTemplate gt){
  3. Program program = super.createProgram(resource, reader, textMap, cr, gt);
  4. modifyStatemetn(resource,program,gt);
  5. return program;
  6. }
  7. private void modifyStatemetn(Resource resource,Program program,GroupTemplate gt){
  8. Statement[] sts = program.metaData.statements;
  9. StatementParser parser = new StatementParser(sts, gt, resource.getId());
  10. parser.addListener(WhileStatement.class, new RestrictLoopNodeListener());
  11. parser.addListener(GeneralForStatement.class, new RestrictLoopNodeListener());
  12. parser.parse();
  13. }
  14. }

继承FastRuntimeEngine有所不同,因为改引擎会copy出一个脚本做分析优化,因此,俩个脚本都需要做修改

  1. public class OnlineTemplateEngine extends FastRuntimeEngine{
  2. public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr,GroupTemplate gt){
  3. FilterProgram program = (FilterProgram)super.createProgram(resource, reader, textMap, cr, gt);
  4. modifyStatemetn(resource,program,gt);
  5. modifyStatemetn(resource,program.getCopy(),gt);
  6. return program;
  7. }
  8. }
  1. class RestrictLoopNodeListener implements Listener{
  2. @Override
  3. public Object onEvent(Event e){
  4. Stack stack = (Stack) e.getEventTaget();
  5. Object o = stack.peek();
  6. if (o instanceof GeneralForStatement){
  7. GeneralForStatement gf = (GeneralForStatement) o;
  8. RestrictForStatement rf = new RestrictForStatement(gf);
  9. return rf;
  10. }else{
  11. return null;
  12. }
  13. }
  14. }

该监听器返回一个新的RestrictForStatement 类,用来替换来的GeneralForStatement。如果返回null,则不需替换。这通常发生在你仅仅通过修改该类的某些属性就可以的场景

完成这些代码后,在配置文件中申明使用新的引擎

  1. ENGINE=org.bee.tl.online.VarRefTemplateEngine

这样就完成了模板引擎定制。

另外一种定制模板引擎方法(2.7.22)

在2.7.21 版本后,提供了另外一种定制模板引擎的方法,可以在Beetl语法树生成的时候提供定制(上面那种是在生成后),这种方法更灵活。但需要对语法树有所了解。

首先需要创建一个引擎

  1. ENGINE=org.bee.tl.online.VarRefTemplateEngine

OnlineTemplateEngine 代码如下,

  1. public class VarRefTemplateEngine extends DefaultTemplateEngine {
  2. protected AntlrProgramBuilder getAntlrBuilder(GroupTemplate gt){
  3. AntlrProgramBuilder pb = new AntlrProgramBuilder(gt);
  4. return pb;
  5. }
  6. class VarRefAntlrProgramBuilder extends AntlrProgramBuilder{
  7. public VarRefAntlrProgramBuilder(GroupTemplate gt) {
  8. super(gt);
  9. }
  10. }
  11. }

AntlrProgramBuilder 方法用于构造语法树,有多个Protected方法可以重载,以实现新的实现。