${} 过滤输出

输出表达式的计算结果,并进行过滤,比如:过滤变量中的HTML标签。

格式: ${expression}

示例: ${user.name}

注:HTTL缺省开启了EscapeXmlFilter,以防止HTML注入攻击,参见:安全示例

如果你需要更强的过滤,请自行实现Filter,并配置到value.filters。此处为运行时热点,请注意性能。

如果输出变量的类型为Template,则缺省不过滤,比如:${include("foo.httl")}

 

$!{} 不过滤输出

原样输出表达式的计算结果,不进行任何过滤,通常用于输出HTML片段。

格式: $!{expression}

示例: $!{body}

 

安全示例

HTTL能根椐变量所处的位置,智能使用不同的安全过滤器,比如:

<htmL>
 
	<!-- 这里会将html变量中的引号转成&quot; -->
	<input value="${html}" />
	 
	<script type="text/javascript">
	// 这里会将js变量中的引号转成\",而不是&quot;
	var value ="${js}";
	</script>
	 
	<style type="text/css">
	.div {
		// 这里同样会将css变量中的引号转成\",而不是&quot;
		font-family:"${css}"
	}
	</style>
 
</htmL>

 

 velocity,是一个基于Java的模板引擎,它允许任何人仅仅简单的使用模板语言来引用由java代码定义的对象。 当Velocity应用于web开发时,界面设计人员可以和java程序开发人员同步开发一个遵循MVC架构的web站点。这是很好的一个地方,可以提高很多效率。下面来说下velocity的语法吧。

1. 获取

$!(var):如果有值则输出,如果为空,则不显示;

${var}:如果有值则输出,如果为空,则将该代码原样输出;

 

2. 设置

#set{$var=5}:设置var的值为5;

 

3. 判断

#if{! $var} 判断var为空

#elseif{$var} 判断var不为空

#else{}

#end

 

4. 循环

循环数组或List时:

#foreach($var in list)
   ##输出当前迭代的次数
   $velocityCount
   ${var}
#end

 

5. 判断是否为null

1)使用 #ifnull() 或 #ifnotnull() 
   #ifnull ($foo) 
要使用这个特性必须在velocity.properties文件中加入: 
userdirective = org.apache.velocity.tools.generic.directive.Ifnull 
userdirective = org.apache.velocity.tools.generic.directive.Ifnotnull 

2) 使用null工具判断 
   #if($null.isNull($foo)) 

 

 

Velocity 模板常用场景

此文档主要演示HTTL比较重要的方面,可以做为入门参考。

1. 模板示例

books.httl: HTTL只有六个指令:#set, #if, #else, #for, #break, #macro,并且不会增加

<!--## 定义变量类型,未定义的变量以Object类型处理 -->
<!--#set(User user, List<Book> books)-->
<html>
    <body>
     
        <!--## 定义宏,可当变量或方法执行 -->
        <!--#macro(cover(Book book))-->
            <img alt="${book.title}" src="${book.cover}" />
        <!--#end-->
         
        <!--## 条件判断 -->
        <!--#if(user.role =="admin")-->
        <table>
         
            <!--## 循环输出,类型可省,将基于泛型推导 -->
            <!--#for(Book book : books)-->
            <tr>
                 
                <!--## 变量输出 -->
                <td>${book.title}</td>
                 
                <!--## 执行宏输出,也可以用${cover} -->
                <td>$!{cover(book)}</td>
                 
                <!--## 变量赋值,类型可省,将基于表达式推导 -->
                <!--#set(int price = book.price * book.discount)-->
                <td>${price}</td>
            </tr>
             
            <!--## 条件中断循环 -->
            <!--#break(for.index == 10)-->
             
            <!--## 当循环数据为空时执行 -->
            <!--#else-->
                没有数据。
            <!--#end-->
         
        </table>
        <!--#else(user)-->
            没有权限。
        <!--#else-->
            没有登录。
        <!--#end-->
     
    </body>
</html>

如果你使用Eclipse开发,可以通过下面的设置,让Eclipse用HTML编辑器打开httl文件:

Eclipse > Window > Preferences > General > ContentTypes > Text > HTML > Add > *.httl

java-and-velocity-difference-01

 

2. 配置示例

httl.properties

import.packages+=com.xxx
template.directory=
message.basename=messages
input.encoding=UTF-8
output.encoding=UTF-8
reloadable=false
precompiled=false

其中,+=表示在缺省配置上追加配置,多个值用逗号分隔。

注意,如果使用缺省值,可以不配,缺省值参见: httl-default.properties

 

3. API 示例

BooksServlet.java

import httl.*;
import java.util.*;
 
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("user", user);
parameters.put("books", books);
 
Engine engine = Engine.getEngine();
Template template = engine.getTemplate("/books.httl");
template.render(parameters, response.getOutputStream());

注:缺省配置下,HTTL不依赖任何三方库,只需JDK1.5+即可。

注:缺省必须要用JDK运行,如果只有JRE,请配置为JavassistCompiler

 

4. 扩展示例

HTTL不允许扩展指令,以保持指令集最小化,防止模板语义复杂,所有扩展功能,以方法扩展实现。

比如JSP的自定义标签:<hasPrivilege resource="foo"> ... </hasPrivilege>

在HTTL中用方法扩展实现:#if(hasPrivilege("foo")) ... #end

只需定义静态方法:

package com.xxx;
 
public class PrivilegeMethod {
    public static boolean hasPrivilege(String resource) {
        return Context.getContext().get("loginUserId") != null;
    }
}

并配置:import.methods+=com.xxx.PrivilegeMethod

另外,HTTL支持用普通对象方法的格式,调用静态扩展方法,如:${date.format("yyyy-MM-dd")}

实际调用下面的静态方法:(被调对象作为第一个参数传入,后面的参数原样传入)

public static String format(Date date, String format) {
    return new SimpleDateFormat(format).format(date);
}

 

5. 继承示例

你可以把模板继承的宏覆盖,理解成Java类继承的方法覆盖。

layout.httl

<html>
    <body>
         <table>
            <tr>
                <td>
                    <!--#macro($crumbs)-->
                        <a href="./">Home</a>
                    <!--#end-->
                </td>
            </tr>
            <tr>
                <td>
                    <!--#macro($menus)-->
                        <a href="/users">Users</a><br/>
                        <a href="/books">Books</a><br/>
                    <!--#end-->
                </td>
                <td>
                    <!--#macro($main)-->
                        Building...
                    <!--#end-->
                </td>
            </tr>
        </table>
    </body>
</html>

其中,#macro($crumbs)表示在宏定义的位置上同时输出,相当于#macro(crumbs)加${crumbs}。

books.httl

${extends("/layout.httl")}
 
<!--#macro(crumbs)-->
    ${super.crumbs} &gt; <a href="/books">Books</a>
<!--#end-->
 
<!--#macro(main)-->
    <table>
        <!--#for(Book book : books)-->
        <tr>
            <td>${book.title}</td>
        </tr>
        <!--#end-->
    </table>
<!--#end-->

其中,宏名称相同的覆盖父模板中的宏,宏参数可以和父模板中的宏不同。

没有覆盖的宏,将使用父模板中的宏直接输出。

注意,这里的宏名称前不要带$!符,否则会输出两次。

你可继承多个模板,子模板本身在继承指令前后也可以带内容。

crumbs宏里面的${super.crumbs}表示输出父模板中的crumbs宏。

你也可以基于CoC(Convention over Configuration)规则,自动继承模板:

# 自动继承模板名变量,如果context.get(extends.variable)变量存在,则继承该模板。
# 注意:此模板是从继承模板目录中查找的,即实际为:template.directory  + context.get(extends.variable)
extends.variable=layout
 
# 如果默认模板存在,则继承默认模板。
# 注意:默认模板是从继承模板目录中查找的,即实际为:template.directory + extends.default
extends.default=default.httl
 
# 父模板引用子模板的名称,如在父模板中调用:${nested},将输出子模板的内容。
# 或者父模板中如果有<!--#macro($nested)-->宏,将被子模板替换。
extends.nested=nested

 

6. 安全示例

HTTL能根椐变量所处的位置,智能使用不同的安全过滤器,比如:

<htmL>
 
	<!-- 这里会将html变量中的引号转成&quot; -->
	<input value="${html}" />
	 
	<script type="text/javascript">
	// 这里会将js变量中的引号转成\",而不是&quot;
	var value ="${js}";
	</script>
	 
	<style type="text/css">
	.div {
		// 这里同样会将css变量中的引号转成\",而不是&quot;
		font-family:"${css}"
	}
	</style>
 
</htmL>

你可以通过httl.properties配置不同Filter实现,并且可以通过httl.spi.Switcher扩展点, 增加变量位置和Filter切换方式:(下面是缺省值,不用配置)

value.switchers=httl.spi.switchers.ScriptValueFilterSwitcher,httl.spi.switchers.StyleValueFilterSwitcher
value.filters=httl.spi.filters.EscapeXmlFilter
script.value.filters=httl.spi.filters.EscapeStringFilter
style.value.filters=httl.spi.filters.EscapeStringFilter

HTTL缺省开启了HTML,JS,CSS过滤,以防止用户忘记配置,而导致HTML注入攻击。

 

HTML注入攻击示例:

(1) 属性注入

<input value="$!{foo}" />
 
如果foo变量的值为:
" onload="alert('HACK');
前面第一个引号会把value属性结束,中间的onload就会变成一个合法属性,从而执行注入的alert语句。

 

(2) 脚本注入

<script type="text/javascript">
var value ="$!{foo}";
</script>
 
如果foo变量的值为:
";alert('HACK');//
前面的引号和分号会把value赋值结束,后面的双斜杠后会注释掉同行语句,从而执行中间注入的alert语句。

 

(3) 样式注入

<style type="text/css">
.div {
    font-family:"$!{foo}"
}
</style>
 
如果foo变量的值为:
";star:expression_r(onload=function(){alert('HACK');});
前面的引号和分号会把font-family属性结束,从而通过expression_r执行注入的alert语句。

 

(4) 标签注入

<td>$!{foo}</td>
 
如果foo变量的值为:
<script>alert('HACK');</script>
变量内容直接作为html运行,从而执行注入的alert语句。

 

7. 异常示例

HTTL的异常信息通常都包含:出错原因,出错位置,解决办法,上下文信息。

比如变量未声明异常:

java.text.ParseException: Undefined variable"user". 
Please add variable type definition <!--#set(Xxx user)--> in your tempalte.
Occur to offset: 6, line: 2, column: 3, char: u, in: 
/WEB-INF/templates/user.httl
========================================
...${user.name}...
     ^-here
========================================
, stack: java.text.ParseException:
	at httl.spi.translators.DfaParser.parse(DfaParser.java:401)
	at httl.spi.translators.DefaultTranslator.translate(DefaultTranslator.java:109)
  • 出错原因:Undefined variable"user"
  • 出错位置:occur to offset: 6, line: 2, column: 3, char: u
  • 解决办法:Please add variable type definition <!--#set(Xxx user)-->
  • 上下文信息:/WEB-INF/templates/user.httl

 

 

参考推荐

Java Velocity 内置指令 #parse() 的使用方法

Velocity 模板引擎