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) -> &quot;bread&quot; &amp; &quot;butter&quot; 45 * 46 * $xml -> "bread" & "butter" 47 * $esc.xml($xml) -> &quot;bread&quot; &amp; &quot;butter&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 * <tools> 80 * <toolbox scope="application"> 81 * <tool class="org.apache.velocity.tools.generic.EscapeTool"/> 82 * </toolbox> 83 * </tools> 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 }