Since the Apache Velocity template engine has been used as the output of the view layer in the project, in order to solve the XSS vulnerability, HTML escapes the content of the output to the page, which I typically implement in 2 ways:
Use filter Filters , where the httpservletrequestwrapper getparameter () method overloads are performed, HTML escapes at the bottom, and the page is output directly ;
This approach is easy and straightforward, the business code does not need to modify the completion of all the escape work, but also brings the problem: Modify the user's original input data, if you need to use the user's original input data, and have to reverse the righteousness back, very troublesome.
Use the velocity-tools $esc. html () on the page to manually escape:$esc. html ($task. content);
This is a much larger effort and requires an additional $ESC on the escaped variable. html (), easy to omit.
Is there any other way to solve it?
Later I found that Velocity provided us with Escapehtmlreference event handler for HTML escaping before referencing the value output of a variable. However, this HTML escape function needs to be specified by the eventhandler.escape.html.match=//configuration variable prefix, especially for some variables need to escape, some variables do not need to escape the case is very inconvenient; after a period of time, feeling very troublesome, Pollution variable name, uncomfortable;
It happened that this time also used the arttemplate.js front-end template engine, found that the template engine provides 2 kinds of variable output mode:<%= param%> and <%== param%> , where <%= param%> is the default common output (the HTML characters in the variable values are escaped output),<%== param%> is output as-is (without any escaping) It feels so good that it satisfies most of the escaped output and also satisfies a small portion of the non-escaped output, and instead of modifying the business code, the template engine provides a different way of outputting it.
Can you modify the Velocity's syntax and also support a non-escaped output? Isn't that the perfect solution to this problem?
$[ ! ] [{] param [}] Is the Velocity of the default reference definition and output syntax, in order to customize the syntax is easy to use, want to use $#{...} syntax format, and the original syntax only one word difference:!
Default Escape Output $task.content${task.content}$!task.content$! {task.content}//output $ #task is not escaped as is. Content$#{task.content}
So I began to study the velocity of the syntax parsing code, velocity uses the AST syntax tree to parse the template syntax, all the syntax is defined in the parser.jjt file, and then use the JAVACC Compile the Parser.jjt file to generate the syntax parsing code (astaddnode,asteqnode, astreference, astsetdirective , etc.),$[!] [ { ] ... [}] The syntax velocity is defined as Reference, so Astreference.java is used to handle Reference .
This syntax configuration is defined in the 730 lines of the Parser.jjt file:
<dollarbang: ("\ \") * "$" ("\ \") * "!" >
This syntax is used to support $! If I turn the last "! " into a ("!" | " do not support $#, hehe, modify it (of course, other grammatical definitions must be looked at again):
<dollarbang: ("\ \") * "$" ("\ \") * ("!" | " # ") >
Found in the Getroot () method of the Astreference.java file $! and $!{ 's treatment:
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); }}
With the above understanding, it is easy to modify the velocity's reference grammar rules, quickly modify the Parser.jjt file, and then use the JAVACC (I use the Javacc-eclipse plugin) Compiled PARSER.JJT, generated the same as the velocity src under the source of the AST code structure, because I modified only the Reference syntax, so the generated code file I only retained the Parsertokenmanager.java file (to replace the src file, This file code a lot, there is no place to upload attachments, so the code is not posted ), the other files using the original src file ( Note: Most of the AST files under the original src are modified by manual modification, so the AST code files generated by the new compilation cannot be fully used .
At the same time, the code in the original Astreference.java getroot () method is expanded to support the processing of $# :
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. The Title OR $ #provider. Title */return t.image.substring (2);}}
Then test, everything OK, so Velocity has one more syntax:$#[{] ... [}] support.
Oh, it's not over yet, huh? $#{} just like $!{} , the following 2 commands are processed:$!{} --escape output, $#{} --do not escape output:
In order not to modify the original core code, easy to expand, here with the extension Velocity provided by the referenceinsertioneventhandler interface implementation (similar to escapehtmlreference), Directly on the code and configuration:
import org.apache.velocity.app.event.referenceinsertioneventhandler;import org.apache.velocity.runtime.runtimeservices;import org.apache.velocity.util.runtimeservicesaware;/** * html Escape Output */public class VelocityEscapeHtmlOutput implements referenceinsertioneventhandler, runtimeservicesaware{private runtimeservices rs = Null;public object referenceinsert (String reference, object value) { // Oh, here ... generally with $ #开头的reference, its value directly return (^_^) if (reference.startswith ("$#")) {return value;} Other default Escape return escapehtml (value);} Public void setruntimeservices (Runtimeservices rs) {this.rs = rs;} Protected runtimeservices getruntimeservices () {return this.rs;} /** * Escape HTML strings * @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);// remove invisible characters if ((int) c < 32) {continue;} Switch (c) {case ' < ': Sb.append ("& #60;"); break;case ' > ': Sb.append ("& #62;"); break;case ' & ': Sb.append ("& #38;"); break;case ' "': Sb.append (" & #34; "); break;case ': Sb.append ("& #39;"); break;case '/': Sb.append ("& #47;"); Break;default:sb.append (c); break;}} Str = null;return sb.tostring ();}}
The EventHandler interface provided by Velocity needs to be configured in velocity.properties to take effect:
Eventhandler.referenceinsertion.class = Com.xxx.VelocityEscapeHtmlOutput
So far, the Reference syntax for Velocity has been transformed! Enjoy it!
Transform Velocity template engine let $[!] {} output is HTML escaped by default and adds $#{} syntax to support non-escaping output