View Javadoc

1   package org.apache.velocity.tools.generic;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.net.URLEncoder;
23  import java.io.UnsupportedEncodingException;
24  import org.apache.commons.lang.StringEscapeUtils;
25  import org.apache.velocity.tools.config.DefaultKey;
26  
27  /**
28   * Tool for working with escaping in Velocity templates.
29   * It provides methods to escape outputs for Velocity, Java, JavaScript, HTML, HTTP, XML and SQL.
30   * Also provides methods to render VTL characters that otherwise needs escaping.
31   *
32   * <p><pre>
33   * Example uses:
34   *  $velocity                    -> Please escape $ and #!
35   *  $esc.velocity($velocity)     -> Please escape ${esc.d} and ${esc.h}!
36   *
37   *  $java                        -> He didn't say, "Stop!"
38   *  $esc.java($java)             -> He didn't say, \"Stop!\"
39   *
40   *  $javascript                  -> He didn't say, "Stop!"
41   *  $esc.javascript($javascript) -> He didn\'t say, \"Stop!\"
42   *
43   *  $html                        -> "bread" & "butter"
44   *  $esc.html($html)             -> &amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;
45   *
46   *  $xml                         -> "bread" & "butter"
47   *  $esc.xml($xml)               -> &amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;
48   *
49   *  $sql                         -> McHale's Navy
50   *  $esc.sql($sql)               -> McHale''s Navy
51   *
52   *  $url                         -> hello here & there
53   *  $esc.url                     -> hello+here+%26+there
54   *
55   *  $esc.dollar                  -> $
56   *  $esc.d                       -> $
57   *
58   *  $esc.hash                    -> #
59   *  $esc.h                       -> #
60   *
61   *  $esc.backslash               -> \
62   *  $esc.b                       -> \
63   *
64   *  $esc.quote                   -> "
65   *  $esc.q                       -> "
66   *
67   *  $esc.singleQuote             -> '
68   *  $esc.s                       -> '
69   *
70   *  $esc.newline                 -> 
71   *
72   *  $esc.n                       -> 
73   *
74   *
75   *  $esc.exclamation             -> !
76   *  $esc.e                       -> !
77   *
78   * Example tools.xml config (if you want to use this with VelocityView):
79   * &lt;tools&gt;
80   *   &lt;toolbox scope="application"&gt;
81   *     &lt;tool class="org.apache.velocity.tools.generic.EscapeTool"/&gt;
82   *   &lt;/toolbox&gt;
83   * &lt;/tools&gt;
84   * </pre></p>
85   *
86   * <p>This tool is entirely threadsafe, and has no instance members.
87   * It may be used in any scope (request, session, or application).
88   * </p>
89   *
90   * @author <a href="mailto:shinobu@ieee.org">Shinobu Kawai</a>
91   * @version $Id: $
92   * @since VelocityTools 1.2
93   * @see StringEscapeUtils
94   */
95  @DefaultKey("esc")
96  public class EscapeTool extends SafeConfig
97  {
98      public static final String DEFAULT_KEY = "esc";
99      
100     private String key = DEFAULT_KEY;
101 
102     /**
103      * Does the actual configuration. This is protected, so
104      * subclasses may share the same ValueParser and call configure
105      * at any time, while preventing templates from doing so when 
106      * configure(Map) is locked.
107      */
108     protected void configure(ValueParser values)
109     {
110         String altkey = values.getString("key");
111         if (altkey != null)
112         {
113             setKey(altkey);
114         }
115     }
116 
117     /**
118      * Sets the key under which this tool has been configured.
119      * @see #velocity
120      */
121     protected void setKey(String key)
122     {
123         if (key == null)
124         {
125             throw new NullPointerException("EscapeTool key cannot be null");
126         }
127         this.key = key;
128     }
129 
130     /**
131      * Should return the key under which this tool has been configured.
132      * The default is 'esc'.
133      * @see #velocity
134      */
135     public String getKey()
136     {
137         return this.key;
138     }
139 
140 
141     /**
142      * <p>Escapes the characters in a <code>String</code> using "poor man's
143      * escaping" for Velocity templates by replacing all '$' characters
144      * with '${esc.d}' and all '#' characters with '${esc.h}'.  This form
145      * of escaping is far more reliable and consistent than using '\' to
146      * escape valid references, directives and macros, though it does require
147      * that you have the EscapeTool available in the context when you later
148      * go to process the result returned by this method.
149      * </p><p>
150      * <b>NOTE</b>: This will only work so long as the EscapeTool is placed
151      * in the context using its default key 'esc' <i>or</i> you are using
152      * VelocityTools 2.0+ and have put this tool in one of your toolboxes
153      * under an alternate key (in which case the EscapeTool will automatically
154      * be told what its new key is).  If for some strange reason you wish
155      * to use an alternate key and are not using the tool management facilities
156      * of VelocityTools 2.0+, you must subclass this tool and manually call
157      * setKey(String) before using this method.
158      * </p>
159      *
160      * @param obj the string value that needs escaping
161      * @return String with escaped values, <code>null</code> if null string input
162      */
163     public String velocity(Object obj)
164     {
165         if (obj == null)
166         {
167             return null;
168         }
169         String string = String.valueOf(obj);
170         // must escape $ first, so we don't escape our hash escapes!
171         return string.replace("$", "${"+getKey()+".d}")
172                      .replace("#", "${"+getKey()+".h}");
173     }
174 
175     /**
176      * Escapes the characters in a <code>String</code> using Java String rules.
177      * <br />
178      * Delegates the process to {@link StringEscapeUtils#escapeJava(String)}.
179      *
180      * @param string the string to escape values, may be null
181      * @return String with escaped values, <code>null</code> if null string input
182      *
183      * @see StringEscapeUtils#escapeJava(String)
184      */
185     public String java(Object string)
186     {
187         if (string == null)
188         {
189             return null;
190         }
191         return StringEscapeUtils.escapeJava(String.valueOf(string));
192     }
193     
194     /**
195      * Escapes the characters in a <code>String</code> using java.util.Properties rules for escaping property keys.
196      *
197      * @param string the string to escape values, may be null
198      * @return String with escaped values, <code>null</code> if null string input
199      * @see #dumpString(String, boolean)
200      */
201     public String propertyKey(Object string)
202     {
203         if (string == null)
204         {
205             return null;
206         }
207         return dumpString(String.valueOf(string), true);
208     }
209     
210     /**
211      * Escapes the characters in a <code>String</code> using java.util.Properties rules for escaping property values.
212      *
213      * @param string the string to escape values, may be null
214      * @return String with escaped values, <code>null</code> if null string input
215      * @see #dumpString(String, boolean)
216      */
217     public String propertyValue(Object string)
218     {
219         if (string == null)
220         {
221             return null;
222         }
223         return dumpString(String.valueOf(string), false);
224     }
225     
226     /**
227      * This code was pulled from the Apache Harmony project.  See
228      * https://svn.apache.org/repos/asf/harmony/enhanced/classlib/trunk/modules/luni/src/main/java/java/util/Properties.java
229      */
230     protected String dumpString(String string, boolean key) {
231         StringBuilder builder = new StringBuilder();
232         int i = 0;
233         if (!key && i < string.length() && string.charAt(i) == ' ') {
234             builder.append("\\ "); //$NON-NLS-1$
235             i++;
236         }
237 
238         for (; i < string.length(); i++) {
239             char ch = string.charAt(i);
240             switch (ch) {
241                 case '\t':
242                     builder.append("\\t"); //$NON-NLS-1$
243                     break;
244                 case '\n':
245                     builder.append("\\n"); //$NON-NLS-1$
246                     break;
247                 case '\f':
248                     builder.append("\\f"); //$NON-NLS-1$
249                     break;
250                 case '\r':
251                     builder.append("\\r"); //$NON-NLS-1$
252                     break;
253                 default:
254                     if ("\\#!=:".indexOf(ch) >= 0 || (key && ch == ' ')) {
255                         builder.append('\\');
256                     }
257                     if (ch >= ' ' && ch <= '~') {
258                         builder.append(ch);
259                     } else {
260                         String hex = Integer.toHexString(ch);
261                         builder.append("\\u"); //$NON-NLS-1$
262                         for (int j = 0; j < 4 - hex.length(); j++) {
263                             builder.append("0"); //$NON-NLS-1$
264                         }
265                         builder.append(hex);
266                 }
267             }
268         }
269         return builder.toString();
270     } 
271 
272     /**
273      * Escapes the characters in a <code>String</code> using JavaScript String rules.
274      * <br />
275      * Delegates the process to {@link StringEscapeUtils#escapeJavaScript(String)}.
276      *
277      * @param string the string to escape values, may be null
278      * @return String with escaped values, <code>null</code> if null string input
279      *
280      * @see StringEscapeUtils#escapeJavaScript(String)
281      */
282     public String javascript(Object string)
283     {
284         if (string == null)
285         {
286             return null;
287         }
288         return StringEscapeUtils.escapeJavaScript(String.valueOf(string));
289     }
290 
291     /**
292      * Escapes the characters in a <code>String</code> using HTML entities.
293      * <br />
294      * Delegates the process to {@link StringEscapeUtils#escapeHtml(String)}.
295      *
296      * @param string the string to escape, may be null
297      * @return a new escaped <code>String</code>, <code>null</code> if null string input
298      *
299      * @see StringEscapeUtils#escapeHtml(String)
300      */
301     public String html(Object string)
302     {
303         if (string == null)
304         {
305             return null;
306         }
307         return StringEscapeUtils.escapeHtml(String.valueOf(string));
308     }
309 
310     /**
311      * Escape the characters in a <code>String</code> to be suitable to use as an HTTP parameter value.
312      * <br/>
313      * Uses UTF-8 as default character encoding.
314      * @param string the string to escape, may be null
315      * @return a new escaped <code>String</code>, <code>null</code> if null string input
316      *
317      * See java.net.URLEncoder#encode(String,String).
318      * @since VelocityTools 1.3
319      */
320     public String url(Object string) {
321         if (string == null) {
322             return null;
323         }
324         try {
325             return URLEncoder.encode(String.valueOf(string),"UTF-8");
326         } catch(UnsupportedEncodingException uee) {
327             return null;
328         }
329     }
330 
331     /**
332      * Escapes the characters in a <code>String</code> using XML entities.
333      * <br />
334      * Delegates the process to {@link StringEscapeUtils#escapeXml(String)}.
335      *
336      * @param string the string to escape, may be null
337      * @return a new escaped <code>String</code>, <code>null</code> if null string input
338      *
339      * @see StringEscapeUtils#escapeXml(String)
340      */
341     public String xml(Object string)
342     {
343         if (string == null)
344         {
345             return null;
346         }
347         return StringEscapeUtils.escapeXml(String.valueOf(string));
348     }
349 
350     /**
351      * Escapes the characters in a <code>String</code> to be suitable to pass to an SQL query.
352      * <br />
353      * Delegates the process to {@link StringEscapeUtils#escapeSql(String)}.
354      *
355      * @param string the string to escape, may be null
356      * @return a new String, escaped for SQL, <code>null</code> if null string input
357      *
358      * @see StringEscapeUtils#escapeSql(String)
359      */
360     public String sql(Object string)
361     {
362         if (string == null)
363         {
364             return null;
365         }
366         return StringEscapeUtils.escapeSql(String.valueOf(string));
367     }
368 
369     /**
370      * Converts the specified Unicode code point and/or escape sequence into
371      * the associated Unicode character.  This allows numeric
372      * code points or String versions of the numeric code point to be correctly
373      * translated within a template.  This is especially useful for those
374      * creating unicode from a reference value, or injecting a unicode character
375      * into a template with a version of Velocity prior to 1.6.
376      * @param code the code to be translated/escaped, may be null
377      * @return the unicode character for that code, {@code null} if input was null
378      * @see Character#toChars(int codePoint)
379      */
380     public String unicode(Object code)
381     {
382         if (code == null)
383         {
384             return null;
385         }
386 
387         String s = String.valueOf(code);
388         if (s.startsWith("\\u"))
389         {
390             s = s.substring(2, s.length());
391         }
392         int codePoint = Integer.valueOf(s, 16);
393         return String.valueOf(Character.toChars(codePoint));
394     }
395             
396 
397     /**
398      * Renders a dollar sign ($).
399      * @return a dollar sign ($).
400      * @see #getD()
401      */
402     public String getDollar()
403     {
404         return "$";
405     }
406 
407     /**
408      * Renders a dollar sign ($).
409      * @return a dollar sign ($).
410      * @see #getDollar()
411      */
412     public String getD()
413     {
414         return this.getDollar();
415     }
416 
417     /**
418      * Renders a hash (#).
419      * @return a hash (#).
420      * @see #getH()
421      */
422     public String getHash()
423     {
424         return "#";
425     }
426 
427     /**
428      * Renders a hash (#).
429      * @return a hash (#).
430      * @see #getHash()
431      */
432     public String getH()
433     {
434         return this.getHash();
435     }
436 
437     /**
438      * Renders a backslash (\).
439      * @return a backslash (\).
440      * @see #getB()
441      */
442     public String getBackslash()
443     {
444         return "\\";
445     }
446 
447     /**
448      * Renders a backslash (\).
449      * @return a backslash (\).
450      * @see #getBackslash()
451      */
452     public String getB()
453     {
454         return this.getBackslash();
455     }
456 
457     /**
458      * Renders a double quotation mark (").
459      * @return a double quotation mark (").
460      * @see #getQ()
461      */
462     public String getQuote()
463     {
464         return "\"";
465     }
466 
467     /**
468      * Renders a double quotation mark (").
469      * @return a double quotation mark (").
470      * @see #getQuote()
471      */
472     public String getQ()
473     {
474         return this.getQuote();
475     }
476 
477     /**
478      * Renders a single quotation mark (').
479      * @return a single quotation mark (').
480      * @see #getS()
481      */
482     public String getSingleQuote()
483     {
484         return "'";
485     }
486 
487     /**
488      * Renders a single quotation mark (').
489      * @return a single quotation mark (').
490      * @see #getSingleQuote()
491      */
492     public String getS()
493     {
494         return this.getSingleQuote();
495     }
496 
497     /**
498      * Renders a new line character appropriate for the
499      * operating system ("\n" in java).
500      * @see #getN()
501      */
502     public String getNewline()
503     {
504         return "\n";
505     }
506 
507     /**
508      * Renders a new line character appropriate for the
509      * operating system ("\n" in java).
510      * @see #getNewline()
511      */
512     public String getN()
513     {
514         return this.getNewline();
515     }
516 
517     /**
518      * Renders an exclamation mark (!).
519      * @return an exclamation mark (!).
520      * @see #getE()
521      */
522     public String getExclamation()
523     {
524         return "!";
525     }
526 
527     /**
528      * Renders an exclamation mark (!).
529      * @return an exclamation mark (!).
530      * @see #getExclamation()
531      */
532     public String getE()
533     {
534         return this.getExclamation();
535     }
536 
537 }