1 package com.atlassian.templaterenderer;
2
3 import com.atlassian.templaterenderer.annotations.HtmlSafe;
4
5 import java.io.IOException;
6 import java.io.StringWriter;
7 import java.io.Writer;
8
9 /**
10 * Escaping that was ripped from Apache Commons-Lang StringUtils class, modified to escape the '<' character as a
11 * unicode string as described in AG-1005.
12 */
13 public class JavaScriptEscaper {
14 /**
15 * <p>
16 * Escapes the characters in a <code>String</code> using JavaScript String
17 * rules.
18 * </p>
19 * <p>
20 * Escapes any values it finds into their JavaScript String literal form. Deals
21 * correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)
22 * </p>
23 *
24 * <p>
25 * So a tab becomes the characters <code>'\\'</code> and <code>'t'</code>.
26 * </p>
27 *
28 * <p>
29 * The only difference between Java strings and JavaScript strings is that
30 * in JavaScript, a single quote must be escaped.
31 * </p>
32 *
33 * <p>
34 * Example:
35 *
36 * <pre>
37 * input string: He didn't say, "Stop!"
38 * output string: He didn\'t say, \"Stop!\"
39 * </pre>
40 *
41 * @param str String to escape values in, may be null
42 * @return String with escaped values, <code>null</code> if null string
43 * input
44 */
45 @HtmlSafe
46 public static String escape(String str) {
47 return escapeJavaStyleString(str, true);
48 }
49
50 /**
51 * <p>
52 * Escapes the characters in a <code>String</code> using JavaScript String
53 * rules to a <code>Writer</code>.
54 * </p>
55 *
56 * <p>
57 * A <code>null</code> string input has no effect.
58 * </p>
59 *
60 * @param out Writer to write escaped string into
61 * @param str String to escape values in, may be null
62 * @throws IllegalArgumentException if the Writer is <code>null</code>
63 * @throws IOException if error occurs on underlying Writer
64 * @see #escape(java.lang.String)
65 **/
66 @HtmlSafe
67 public static void escape(Writer out, String str) throws IOException {
68 escapeJavaStyleString(out, str, true);
69 }
70
71 /**
72 * <p>
73 * Worker method for the {@link #escape(String)} method.
74 * </p>
75 *
76 * @param str String to escape values in, may be null
77 * @param escapeSingleQuotes escapes single quotes if <code>true</code>
78 * @return the escaped string
79 */
80 private static String escapeJavaStyleString(String str, boolean escapeSingleQuotes) {
81 if (str == null) {
82 return null;
83 }
84 try {
85 StringWriter writer = new StringWriter(str.length() * 2);
86 escapeJavaStyleString(writer, str, escapeSingleQuotes);
87 return writer.toString();
88 } catch (IOException ioe) {
89 // this should never ever happen while writing to a StringWriter
90 ioe.printStackTrace();
91 return null;
92 }
93 }
94
95 /**
96 * <p>
97 * Worker method for the {@link #escape(String)} method.
98 * </p>
99 *
100 * @param out write to receieve the escaped string
101 * @param str String to escape values in, may be null
102 * @param escapeSingleQuote escapes single quotes if <code>true</code>
103 * @throws IOException if an IOException occurs
104 */
105 private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote) throws IOException {
106 if (out == null) {
107 throw new IllegalArgumentException("The Writer must not be null");
108 }
109 if (str == null) {
110 return;
111 }
112 int sz;
113 sz = str.length();
114 for (int i = 0; i < sz; i++) {
115 char ch = str.charAt(i);
116
117 // handle unicode
118 if (ch > 0xfff) {
119 out.write("\\u" + hex(ch));
120 } else if (ch > 0xff) {
121 out.write("\\u0" + hex(ch));
122 } else if (ch > 0x7f) {
123 out.write("\\u00" + hex(ch));
124 } else if (ch < 32) {
125 switch (ch) {
126 case '\b':
127 out.write('\\');
128 out.write('b');
129 break;
130 case '\n':
131 out.write('\\');
132 out.write('n');
133 break;
134 case '\t':
135 out.write('\\');
136 out.write('t');
137 break;
138 case '\f':
139 out.write('\\');
140 out.write('f');
141 break;
142 case '\r':
143 out.write('\\');
144 out.write('r');
145 break;
146 default:
147 if (ch > 0xf) {
148 out.write("\\u00" + hex(ch));
149 } else {
150 out.write("\\u000" + hex(ch));
151 }
152 break;
153 }
154 } else {
155 switch (ch) {
156 case '\'':
157 if (escapeSingleQuote) {
158 out.write('\\');
159 }
160 out.write('\'');
161 break;
162 case '"':
163 out.write('\\');
164 out.write('"');
165 break;
166 case '\\':
167 out.write('\\');
168 out.write('\\');
169 break;
170 case '/':
171 out.write('\\');
172 out.write('/');
173 break;
174 case '<':
175 out.write("\\u003c");
176 break;
177 default:
178 out.write(ch);
179 break;
180 }
181 }
182 }
183 }
184
185 /**
186 * <p>
187 * Returns an upper case hexadecimal <code>String</code> for the given
188 * character.
189 * </p>
190 *
191 * @param ch The character to convert.
192 * @return An upper case hexadecimal <code>String</code>
193 */
194 public static String hex(char ch) {
195 return Integer.toHexString(ch).toUpperCase();
196 }
197 }
198