【问题标题】:Default escaping in FreemarkerFreemarker 中的默认转义
【发布时间】:2023-12-22 09:53:01
【问题描述】:

在 Freemarker 模板中,我们可以使用转义指令自动对包含块内的所有插值应用转义:

<#escape x as x?html>
  <#-- name is escaped as html -->
  Hallo, ${name}
</#escape>

有没有办法以编程方式实现类似的效果,定义应用于模板中所有插值的默认转义,包括那些外部转义指令?

谢谢。

【问题讨论】:

    标签: java xss escaping freemarker


    【解决方案1】:

    从 2.3.24 开始,每个模板都有一个关联的 freemarker.core.OutputFormat 对象,它指定是否以及如何转义 ${...}(和 #{...})。针对 HTML、XML 和 RTF 的OuputFormat 是开箱即用的,但您也可以定义自己的格式。当默认情况下,选定的@ 987654327 eCSEAPES时,您可以防止显式逃逸,如${foo?no_esc}

    有几种方法可以将模板与您想要的OutputFormat 关联起来。对于 HTML 和 XML 转义,推荐的方法是将 recognize_standard_file_extensions 配置设置设置为 true,然后对 HTML 使用 ftlh 文件扩展名,对 XML 模板使用 ftlx 文件扩展名。您还可以使用template_configurers 设置将OutputFormat-s 与基于任意模板名称(模板路径)模式的模板相关联。最后同样重要的是,您可以像configuration.setOutputFormat(HTMLOutputFormat.INSTANCE) 这样全局设置默认输出格式。您还可以将模板顶部的输出格式覆盖为&lt;#ftl output_format='HTML'&gt;,尽管它应该很少使用。

    相关文档页面:http://freemarker.org/docs/dgui_misc_autoescaping.htmlhttp://freemarker.org/docs/pgui_config_outputformatsautoesc.html

    【讨论】:

      【解决方案2】:

      详细说明 Attila 的回答:您可以使用 this one 之类的类,然后像这样包装您的模板加载器:

      final TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass(), templatePath) {
        /**
         * Replaces the normal template reader with something that changes the default
         * escaping to HTML as to avoid XSS attacks.
         */
        @Override
        public Reader getReader(Object templateSource, String encoding) throws IOException {
           return new WrappingReader(super.getReader(templateSource, encoding), "<#escape x as x?html>", "</#escape>");
        }
      };
      

      如果您在添加的部分中不包含换行符,则不会遇到行号问题。但是,您不能在这种方法中使用 /[#ftl]。

      【讨论】:

      【解决方案3】:

      如果您使用 在模板中包含例如 HTML,则链接中建议的 TemplateLoaders 需要稍作调整。

      此外,您需要复制 spring.ftl 并使用您自己的副本,并像 Tom 所说的那样删除顶部的 指令。

      下面的效果很好,虽然有点粗糙(在 commons-io 上使用 guava)

      @Override  
      public Reader getReader(Object pTemplateSource, String pEncoding) throws IOException {  
         Reader tReader = delegate.getReader(pTemplateSource, pEncoding);  
         try {  
             String tTemplateText = CharStreams.toString(tReader);
      
             //only include files ending with "ftl", as we may have some parse=false on included html files
             if (pTemplateSource.toString().endsWith("ftl")) {
                 return new StringReader(ESCAPE_PREFIX + tTemplateText + ESCAPE_SUFFIX);
             }
             return new StringReader(tTemplateText);
         } finally {  
             Closeables.closeQuietly(tReader);  
         }  
      } 
      

      【讨论】:

        【解决方案4】:

        您实际上不需要 WrappingReader 来添加转义。您可以在任何 TemplateLoader 周围创建一个装饰器,将模板读入字符串,将模板文本包装在转义中,然后返回读取结果字符串的 StringReader。要了解这是如何完成的,请查看 here。我发现的唯一问题是,如果您使用这种方法并从类路径中包含 spring.ftl 宏,它们将会崩溃,因为它们在最顶部有一个 声明。但是,您可以简单地将 spring.ftl 复制到您的模板路径中并删除声明(以及所有转义指令,因为您将默认转义)。

        【讨论】:

          【解决方案5】:

          有一个解决方案,尽管它并不完全是微不足道的。您可以创建一个特殊的 TemplateLoader 来包装其他模板加载器,并在模板源文本的 prolog 中注入 ,并作为其结尾添加。

          明显的缺点: - 第一行的列号将被丢弃 - 如果您的模板以 声明开头,则需要在其后插入 。

          【讨论】: