改造Velocity模板引擎让$[!]{}输出默认进行html转义,并增加$#{}语法支持不转义输出

一直以来在项目中使用Apache Velocity模板引擎作为视图层输出,为了解决XSS漏洞,需要对输出到页面的内容进行HTML转义,我一般采用2种方式实现:

  1. 使用过滤器 Filter,在其中进行 HttpServletRequestWrapper 的 getParameter( )等方法重载,在底层进行HTML转义,然后页面直接输出;

    这种方式很轻松很直接,业务代码不需要修改就完成了所有的转义工作;但是也带来了问题:修改了用户的原始输入数据,如果需要用到用户的原始输入数据,又得反转义回去,很麻烦。

  2. 在页面上使用 velocity-tools$esc.html( ) 进行手工转义:$esc.html($task.content);

    这种方式工作量比较大,需要在转义的变量上增加 $esc.html(),很容易遗漏。

那有没有别的方法来解决呢?

后来我发现 Velocity 为我们提供了EscapeHtmlReference  event handler 用于在引用变量值输出之前进行HTML转义处理,但是这个HTML转义功能需要通过eventhandler.escape.html.match=//配置指定变量前缀来进行,特别对于有的变量需要转义,有的变量不需要转义的情况下非常的不方便;在用了一段时间后,感觉很麻烦,污染变量命名,不爽;

正巧这段时间也使用了 artTemplate.js 的前端模板引擎,发现这个模板引擎提供了2种变量输出方式:<%= param %><%== param %>,其中 <%= param %> 是默认的常用输出方式(会对变量值中的HTML字符进行转义输出),<%== param %> 则是原样输出(不进行任何转义);感觉这样的方式非常的好,即满足了大部分的转义输出,也满足了小部分的非转义输出,而且不用对业务代码进行修改,而是由模板引擎提供不同的输出方法。

那能否修改 Velocity 的语法,也支持一种非转义输出呢?这样不就完美解决这个问题了吗?

$[ ! ][ { ] param [ } ] 是 Velocity 的默认引用定义和输出语法,为了自定义的语法简单易用,想采用 $#{ ... } 语法格式,这样和原来的语法只有一个字的差别:! -> # 

// 默认转义输出
$task.content
${task.content}
$!task.content
$!{task.content}

// 原样不转义输出
$#task.content
$#{task.content}

于是我开始研究 Velocity 的语法解析代码,Velocity 采用 AST 语法树进行模板的语法解析的,所有的语法定义在 Parser.jjt 文件中,然后使用JAVACC 编译Parser.jjt文件生成语法解析代码(ASTAddNode,ASTEQNode, ASTReference, ASTSetDirective等等),$[ ! ][ { ] ... [ } ] 语法Velocity定义为 Reference,因此 ASTReference.java 就是用来处理 Reference 的。

在 Parser.jjt 文件的 730 行定义了这样的语法配置:

<DOLLARBANG: ("\\")* "$" ("\\")* "!">

这段语法是用来支持 $! 的,如果我把最后的 "!" 变成 ("!"|"#") 不就支持 $# 了嘛,呵呵,修改之(当然其它的语法定义也必须都看一遍):

<DOLLARBANG: ("\\")* "$" ("\\")* ("!"|"#")>

发现在 ASTReference.java 文件的 getRoot() 方法中进行了 $! $!{ 的处理:

if (t.image.startsWith("$!")) {
    referenceType = QUIET_REFERENCE;

    /*
     *  only if we aren‘t escaped do we want to null the output
     */

    if (!escaped) nullString = "";

    if (t.image.startsWith("$!{")) {
	/*
	 *  ex : $!{provider.Title}
	 */
	return t.next.image;
    } else {
	/*
	 *  ex : $!provider.Title
	 */
	return t.image.substring(2);
    }
}

在有了上面的了解后,修改Velocity的Reference 语法规则就简单了,迅速修改 Parser.jjt 文件,然后使用 JAVACC(我使用javacc-eclipse插件) 编译Parser.jjt,生成了和Velocity src 下源码一样的AST代码结构,由于我修改的仅仅是 Reference 的语法,因此生成的代码文件中我只保留了 ParserTokenManager.java 文件(用于替换src的该文件,这个文件代码很多,又没有发现可以上传附件的地方,因此代码就不贴出了),其它的文件使用原始 src 下的文件(注意:原始src下的AST文件大部分被后期手工修改完善,因此不能完全使用新编译生成的AST代码文件)。

同时对原始的 ASTReference.java getRoot() 方法中的代码进行扩充,支持 $# 的处理:

if (t.image.startsWith("$!") || t.image.startsWith("$#")) {
		referenceType = QUIET_REFERENCE;

		/*
		 *  only if we aren‘t escaped do we want to null the output
		 */

		if (!escaped) nullString = "";

		if (t.image.startsWith("$!{") || t.image.startsWith("$#{"))
		{
				/*
				 *  ex : $!{provider.Title} OR $#{provider.Title}
				 */

				return t.next.image;
		}
		else
		{
				/*
				 *  ex : $!provider.Title OR $#provider.Title
				 */

				return t.image.substring(2);
		}
}

然后测试,一切OK,这样 Velocity 就多了一个语法:$#[ { ] ... [ } ] 支持。

哦,还没完呢,呵呵,此时 $#{} 仅仅和 $!{} 一样,下面开始对这2个命令进行处理:$!{} -- 转义输出, $#{} -- 不转义输出:

为了不修改原始核心代码,方便扩展,这里采用扩展 Velocity 提供的 ReferenceInsertionEventHandler 接口实现进行(类似EscapeHtmlReference),直接上代码和配置:

import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.util.RuntimeServicesAware;

/**
 * HTML转义输出
 */
public class VelocityEscapeHtmlOutput 
	implements ReferenceInsertionEventHandler, RuntimeServicesAware
{
	private RuntimeServices rs = null;

	public Object referenceInsert(String reference, Object value) 
	{
	       // 呵呵,这里... 凡是以 $#开头的reference,其值直接返回(^_^)
		if(reference.startsWith("$#")) {
			return value;
		}
		// 其它默认转义
		return escapeHtml(value);
	}

	public void setRuntimeServices(RuntimeServices rs) 
	{
		this.rs = rs;
	}

	protected RuntimeServices getRuntimeServices()
	{
		return this.rs;
	}

	/**
	 * 转义HTML字符串
	 * @param str
	 * @return
	 */
	private static Object escapeHtml(Object value)
	{
		if(value == null)
		{
			return null;
		}

		if(!(value instanceof String))
		{
			return value;
		}

		String str = value.toString();
		StringBuilder sb = new StringBuilder(str.length() + 30);

		for(int i = 0, len = str.length(); i < len; i++)
		{
			char c = str.charAt(i);
			// 去除不可见字符
			if((int)c < 32)
			{
				continue;
			}

			switch(c)
			{
				case ‘<‘:
					sb.append("<");
					break;
				case ‘>‘:
					sb.append(">");
					break;
				case ‘&‘:
					sb.append("&");
					break;
				case ‘"‘:
					sb.append(""");
					break;
				case ‘\‘‘:
					sb.append("'");
					break;
				case ‘/‘:
					sb.append("/");
					break;
				default:
					sb.append(c);
					break;
			}
		}

		str = null;

		return sb.toString();
	}
}

Velocity 提供的EventHandler接口需要在 velocity.properties中进行配置才能生效:

eventhandler.referenceinsertion.class = com.xxx.VelocityEscapeHtmlOutput

到此为止,就完成了对 Velocity 的 Reference 语法改造了!尽情享用吧!

时间: 2024-08-04 23:08:42

改造Velocity模板引擎让$[!]{}输出默认进行html转义,并增加$#{}语法支持不转义输出的相关文章

使用 Velocity 模板引擎快速生成代码

原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-velocity1/ Velocity 是一个基于 Java 的模板引擎,它允许任何人仅仅简单的使用模板语言来引用由 Java 代码定义的对象,从而实现界面和 Java 代码的分离,使得界面设计人员可以和 Java 程序开发人员同步开发一个遵循 MVC 架构的 web 站点.但是在实际应用过程中,Velocity 又不仅仅被用在了 MVC 的架构中. Velocity 模板引擎介绍 在现今的软

转 如何使用velocity模板引擎开发网站

基于 Java 的网站开发,很多人都采用 JSP 作为前端网页制作的技术,尤其在是国内.这种技术通常有一些问题,我试想一下我们是怎样开发网站的,通常有几种方法: 1:功能确定后,由美工设计网页的UI(界面)部分,然后由程序员在其上加入代码显示逻辑(比如循环,判断显示数据结果).也就是通常的 jsp 页面制作,当然这部分可以由美工完成模板,然后由 jsp 工程师再继续以它为原型创建相应的 jsp 页面. 2:功能确定后,由美工设计网页的UI(界面)部分,然后由网页制作人员在其上加入代码显示逻辑(比

velocity模板引擎学习(3)-异常处理

按上回继续,前面写过一篇Spring MVC下的异常处理.及Spring MVC下的ajax异常处理,今天看下换成velocity模板引擎后,如何处理异常页面: 一.404错误.500错误 1 <error-page> 2 <error-code>404</error-code> 3 <location>/nopage.do</location> 4 </error-page> 5 6 <error-page> 7 <

Velocity模板引擎的简单使用

一 简介 Velocity 是一个基于 Java 的模板引擎,它允许任何人仅仅简单的使用模板语言来引用由 Java 代码定义的对象,从而实现界面和 Java 代码的分离,使得界面设计人员可以和 Java 程序开发人员同步开发一个遵循 MVC 架构的 web 站点. 二 一个简单示例 (1)从官网下载最新的jar包,然后新建一个普通的Java工程,然后导入下载下来的zip文件中的jar包.当然也可以使用我用过的jar包,链接:http://pan.baidu.com/s/1PXu5g (2)新建一

[JavaWeb基础] 019.Velocity 模板引擎简单示例

1.什么是Velocity 一种J2EE的前端模版技术,和JSP,Freemarker差不多,都是用来展示网页内容的.和JSP不同的是velocity只能显示Action中的数据,不能处理数据.不能写java代码,但是可以使用Velocity标记.也就是说把显示代码和后端的JAVA代码分离开来,降低程序的耦合性 2.需要引入哪些Jar包 velocity-1.5.jar,velocity-1.6.2.jar,velocity-tools-2.0.jar,velocity-tools-generi

Velocity 模板引擎介绍

一.变量 1. 变量定义 #set($name =“velocity”) 2. 变量的使用 在模板文件中使用$name 或者${name} 来使用定义的变量.推荐使用${name} 这种格式,因为在模板中同时可能定义了类似$name 和$names 的两个变量,如果不选用大括号的话,引擎就没有办法正确识别$names 这个变量. 对于一个复杂对象类型的变量,例如$person,可以使用${person.name} 来访问 person 的 name 属性.值得注意的是,这里的${person.n

velocity模板引擎学习(1)

velocity与freemaker.jstl并称为java web开发三大标签技术,而且velocity在codeplex上还有.net的移植版本NVelocity,(注:castle团队在github上也维护了一个版本)对于使用异构技术的团队(即要搞.NET又要搞JAVA),总是希望找一种通用的技术,兼容所有技术平台,以便降低学习成本,无疑velocity是一种值得考虑的选择. 一.与strtus2的集成 1 <dependency> 2 <groupId>org.apache

velocity模板引擎学习(2)-velocity tools 2.0

使用velocity后,原来的很多标签无法使用了,必须借助velocity tools来完成,目前velocity tools最新版本是2.0,下面是velocity tools的一些注意事项: 1. 与Spring MVC 3.x/4.x的集成问题 Spring 3.x/4.x只支持1.3.x的velocity tools,要使用2.0必须自己扩展VelocityToolboxView类 1 package org.springframework.web.servlet.view.veloci

(二)模板引擎之Velocity脚本基本语法全

velocity velocity三种reference 变量:对java对象的一种字符串化表示,返回值调用了java的toString()方法的结果. 方法:调用的是对象的某个方法,该方法必须是public的,返回值也是toString(),方法的参数也必须为String的. 属性:除了访问java的类属性外,等价于get..()方法. 基本符号 1."#"来标识velocity的脚本语句. 2."$"来标识一个对象(或者变量). 3."{}"