1 | // Copyright (C) 2007 Google Inc. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | package com.google.caja.lexer.escaping; |
16 | |
17 | import java.util.regex.Matcher; |
18 | import java.util.regex.Pattern; |
19 | import junit.framework.TestCase; |
20 | |
21 | /** |
22 | * @author mikesamuel@gmail.com (Mike Samuel) |
23 | */ |
24 | public class EscapingTest extends TestCase { |
25 | |
26 | private static final String CHARS; |
27 | static { |
28 | StringBuilder sb = new StringBuilder(150); |
29 | for (int i = 0; i < 133; ++i) { |
30 | sb.append((char) i); |
31 | } |
32 | sb.append('\u200E') // [:Cf:] LRM |
33 | .append('\u200F') // [:Cf:] RLM |
34 | .append('\u2010') // Regular non-ascii codepoint |
35 | .append('\u2028') // newline |
36 | .append('\u2029') // newline |
37 | .appendCodePoint(0x1D120) // Regular supplemental |
38 | .appendCodePoint(0x1D177); // [:Cf:] |
39 | CHARS = sb.toString(); |
40 | } |
41 | private static final String WORD_CHARS; |
42 | static { |
43 | StringBuilder firstAndLastCodepage = new StringBuilder(); |
44 | for (int i = 0; i < 256; ++i) { |
45 | firstAndLastCodepage.append((char) i); |
46 | } |
47 | for (int i = 0xff00; i < 0xffff; ++i) { |
48 | firstAndLastCodepage.append((char) i); |
49 | } |
50 | |
51 | StringBuilder sb = new StringBuilder(); |
52 | Matcher m = Pattern.compile("[\\p{javaLetterOrDigit}_$]+") |
53 | .matcher(firstAndLastCodepage); |
54 | while (m.find()) { |
55 | sb.append(m.group()); |
56 | } |
57 | WORD_CHARS = sb.toString(); |
58 | } |
59 | |
60 | public final void testMinimalEscapeJsString() { |
61 | StringBuilder sb = new StringBuilder(); |
62 | Escaping.escapeJsString(CHARS, false, false, sb); |
63 | assertStringsEqual( |
64 | ("\\x00\001\002\003\004\005\006\007\\b\t\\n\013\014\\r\016\017" |
65 | + "\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" |
66 | + " !\\\"#$%&\\'()*+,-./" |
67 | + "0123456789:;<=>?" |
68 | + "@ABCDEFGHIJKLMNO" |
69 | + "PQRSTUVWXYZ[\\\\]^_" |
70 | + "`abcdefghijklmno" |
71 | + "pqrstuvwxyz{|}~\177" |
72 | + "\u0080\u0081\u0082\u0083\u0084" |
73 | + "\\u200e\\u200f\u2010\\u2028\\u2029" |
74 | + "\ud834\udd20" |
75 | + "\\ud834\\udd77" |
76 | ), |
77 | sb.toString()); |
78 | // Disallowed in strings in Firefox2 |
79 | assertJsEscaped("\\u200c\\u200d\\u200e\\u200f", "\u200C\u200D\u200E\u200F"); |
80 | assertJsEscaped( |
81 | "\\u202a\\u202b\\u202c\\u202d\\u202e", |
82 | "\u202A\u202B\u202C\u202D\u202E"); |
83 | assertJsEscaped( |
84 | "\\u206b\\u206c\\u206d\\u206e\\u206f", |
85 | "\u206B\u206C\u206D\u206E\u206F"); |
86 | assertJsEscaped("\\ufeff", "\uFEFF"); |
87 | // Disallowed in strings in IE6 |
88 | assertJsEscaped( |
89 | "\\ufdd0\\ufdd1\\ufdd2\\ufdd3\\ufdd4\\ufdd5\\ufdd6\\ufdd7" |
90 | + "\\ufdd8\\ufdd9\\ufdda\\ufddb\\ufddc\\ufddd\\ufdde\\ufddf", |
91 | "\uFDD0\uFDD1\uFDD2\uFDD3\uFDD4\uFDD5\uFDD6\uFDD7" |
92 | + "\uFDD8\uFDD9\uFDDA\uFDDB\uFDDC\uFDDD\uFDDE\uFDDF"); |
93 | assertJsEscaped( |
94 | "\\ufde0\\ufde1\\ufde2\\ufde3\\ufde4\\ufde5\\ufde6\\ufde7" |
95 | + "\\ufde8\\ufde9\\ufdea\\ufdeb\\ufdec\\ufded\\ufdee\\ufdef", |
96 | "\uFDE0\uFDE1\uFDE2\uFDE3\uFDE4\uFDE5\uFDE6\uFDE7" |
97 | + "\uFDE8\uFDE9\uFDEA\uFDEB\uFDEC\uFDED\uFDEE\uFDEF"); |
98 | assertJsEscaped( |
99 | "\\ufff0\\ufff1\\ufff2\\ufff3\\ufff4\\ufff5\\ufff6\\ufff7" |
100 | + "\\ufff8\\ufffe\\uffff", |
101 | "\uFFF0\uFFF1\uFFF2\uFFF3\uFFF4\uFFF5\uFFF6\uFFF7" |
102 | + "\uFFF8\uFFFE\uFFFF"); |
103 | } |
104 | private static void assertJsEscaped(String golden, String raw) { |
105 | StringBuilder sb = new StringBuilder(); |
106 | Escaping.escapeJsString(raw, false, false, sb); |
107 | assertStringsEqual(golden, sb.toString()); |
108 | } |
109 | |
110 | public final void testParanoidEscapeJsString() { |
111 | StringBuilder sb = new StringBuilder(); |
112 | Escaping.escapeJsString(CHARS, false, true, sb); |
113 | assertStringsEqual( |
114 | (// all ctrl chars escaped |
115 | "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07" |
116 | + "\\b\\t\\n\\x0b\\f\\r\\x0e\\x0f" |
117 | + "\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17" |
118 | + "\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f" |
119 | + " !\\\"#$%\\x26\\'()*+,-./" // & escaped |
120 | + "0123456789:;\\x3c=\\x3e?" // < and > escaped |
121 | + "@ABCDEFGHIJKLMNO" |
122 | + "PQRSTUVWXYZ[\\\\]^_" |
123 | + "`abcdefghijklmno" |
124 | + "pqrstuvwxyz{|}~\177" |
125 | + "\u0080\u0081\u0082\u0083\u0084" |
126 | + "\\u200e\\u200f\u2010\\u2028\\u2029" |
127 | + "\ud834\udd20" |
128 | + "\\ud834\\udd77" |
129 | ), |
130 | sb.toString()); |
131 | } |
132 | |
133 | public final void testEscapeJsonString() { |
134 | StringBuilder sb = new StringBuilder(); |
135 | Escaping.escapeJsonString(CHARS, false, sb); |
136 | // JSON doesn't allow \x escapes, or \' as an escape sequence. |
137 | assertStringsEqual( |
138 | (// all ctrl chars escaped |
139 | "\\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007" |
140 | + "\\b\\t\\n\u000b\\f\\r\u000e\u000f" |
141 | + "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017" |
142 | + "\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f" |
143 | + " !\\\"#$%\u0026\\u0027()*+,-./" // & escaped |
144 | + "0123456789:;\u003c=\u003e?" // < and > escaped |
145 | + "@ABCDEFGHIJKLMNO" |
146 | + "PQRSTUVWXYZ[\\\\]^_" |
147 | + "`abcdefghijklmno" |
148 | + "pqrstuvwxyz{|}~\177" |
149 | + "\u0080\u0081\u0082\u0083\u0084" |
150 | + "\\u200e\\u200f\u2010\\u2028\\u2029" |
151 | + "\ud834\udd20" |
152 | + "\\ud834\\udd77" |
153 | ), |
154 | sb.toString()); |
155 | } |
156 | |
157 | public final void testAsciiOnlyEscapeJsString() { |
158 | StringBuilder sb = new StringBuilder(); |
159 | Escaping.escapeJsString(CHARS, true, false, sb); |
160 | assertStringsEqual( |
161 | ("\\x00\001\002\003\004\005\006\007\\b\t\\n\013\014\\r\016\017" |
162 | + "\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" |
163 | + " !\\\"#$%&\\'()*+,-./" |
164 | + "0123456789:;<=>?" |
165 | + "@ABCDEFGHIJKLMNO" |
166 | + "PQRSTUVWXYZ[\\\\]^_" |
167 | + "`abcdefghijklmno" |
168 | + "pqrstuvwxyz{|}~\\x7f" // All chars >= 0x7f escaped |
169 | + "\\x80\\x81\\x82\\x83\\x84" |
170 | + "\\u200e\\u200f\\u2010\\u2028\\u2029" |
171 | + "\\ud834\\udd20" |
172 | + "\\ud834\\udd77" |
173 | ), |
174 | sb.toString()); |
175 | } |
176 | |
177 | public final void testParanoidAsciiOnlyEscapeJsString() { |
178 | StringBuilder sb = new StringBuilder(); |
179 | Escaping.escapeJsString(CHARS, true, true, sb); |
180 | assertStringsEqual( |
181 | ("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07" |
182 | + "\\b\\t\\n\\x0b\\f\\r\\x0e\\x0f" |
183 | + "\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17" |
184 | + "\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f" |
185 | + " !\\\"#$%\\x26\\'()*+,-./" |
186 | + "0123456789:;\\x3c=\\x3e?" |
187 | + "@ABCDEFGHIJKLMNO" |
188 | + "PQRSTUVWXYZ[\\\\]^_" |
189 | + "`abcdefghijklmno" |
190 | + "pqrstuvwxyz{|}~\\x7f" |
191 | + "\\x80\\x81\\x82\\x83\\x84" |
192 | + "\\u200e\\u200f\\u2010\\u2028\\u2029" |
193 | + "\\ud834\\udd20" |
194 | + "\\ud834\\udd77" |
195 | ), |
196 | sb.toString()); |
197 | } |
198 | |
199 | public final void testIdentifierEscaping() { |
200 | StringBuilder sb = new StringBuilder(); |
201 | Escaping.escapeJsIdentifier(WORD_CHARS, true, sb); |
202 | assertStringsEqual( |
203 | (// all ctrl chars escaped |
204 | "$0123456789" |
205 | + "ABCDEFGHIJKLMNOPQRSTUVWXYZ_" |
206 | + "abcdefghijklmnopqrstuvwxyz" |
207 | + "\\u00aa\\u00b5\\u00ba" |
208 | + "\\u00c0\\u00c1\\u00c2\\u00c3\\u00c4\\u00c5\\u00c6\\u00c7" |
209 | + "\\u00c8\\u00c9\\u00ca\\u00cb\\u00cc\\u00cd\\u00ce\\u00cf" |
210 | + "\\u00d0\\u00d1\\u00d2\\u00d3\\u00d4\\u00d5\\u00d6" |
211 | + "\\u00d8\\u00d9\\u00da\\u00db\\u00dc\\u00dd\\u00de\\u00df" |
212 | + "\\u00e0\\u00e1\\u00e2\\u00e3\\u00e4\\u00e5\\u00e6\\u00e7" |
213 | + "\\u00e8\\u00e9\\u00ea\\u00eb\\u00ec\\u00ed\\u00ee\\u00ef" |
214 | + "\\u00f0\\u00f1\\u00f2\\u00f3\\u00f4\\u00f5\\u00f6" |
215 | + "\\u00f8\\u00f9\\u00fa\\u00fb\\u00fc\\u00fd\\u00fe\\u00ff" |
216 | + "\\uff10\\uff11\\uff12\\uff13\\uff14\\uff15\\uff16\\uff17" |
217 | + "\\uff18\\uff19" |
218 | + "\\uff21\\uff22\\uff23\\uff24\\uff25\\uff26\\uff27" |
219 | + "\\uff28\\uff29\\uff2a\\uff2b\\uff2c\\uff2d\\uff2e\\uff2f" |
220 | + "\\uff30\\uff31\\uff32\\uff33\\uff34\\uff35\\uff36\\uff37" |
221 | + "\\uff38\\uff39\\uff3a" |
222 | + "\\uff41\\uff42\\uff43\\uff44\\uff45\\uff46\\uff47" |
223 | + "\\uff48\\uff49\\uff4a\\uff4b\\uff4c\\uff4d\\uff4e\\uff4f" |
224 | + "\\uff50\\uff51\\uff52\\uff53\\uff54\\uff55\\uff56\\uff57" |
225 | + "\\uff58\\uff59\\uff5a" |
226 | + "\\uff66\\uff67" |
227 | + "\\uff68\\uff69\\uff6a\\uff6b\\uff6c\\uff6d\\uff6e\\uff6f" |
228 | + "\\uff70\\uff71\\uff72\\uff73\\uff74\\uff75\\uff76\\uff77" |
229 | + "\\uff78\\uff79\\uff7a\\uff7b\\uff7c\\uff7d\\uff7e\\uff7f" |
230 | + "\\uff80\\uff81\\uff82\\uff83\\uff84\\uff85\\uff86\\uff87" |
231 | + "\\uff88\\uff89\\uff8a\\uff8b\\uff8c\\uff8d\\uff8e\\uff8f" |
232 | + "\\uff90\\uff91\\uff92\\uff93\\uff94\\uff95\\uff96\\uff97" |
233 | + "\\uff98\\uff99\\uff9a\\uff9b\\uff9c\\uff9d\\uff9e\\uff9f" |
234 | + "\\uffa0\\uffa1\\uffa2\\uffa3\\uffa4\\uffa5\\uffa6\\uffa7" |
235 | + "\\uffa8\\uffa9\\uffaa\\uffab\\uffac\\uffad\\uffae\\uffaf" |
236 | + "\\uffb0\\uffb1\\uffb2\\uffb3\\uffb4\\uffb5\\uffb6\\uffb7" |
237 | + "\\uffb8\\uffb9\\uffba\\uffbb\\uffbc\\uffbd\\uffbe" |
238 | + "\\uffc2\\uffc3\\uffc4\\uffc5\\uffc6\\uffc7" |
239 | + "\\uffca\\uffcb\\uffcc\\uffcd\\uffce\\uffcf" |
240 | + "\\uffd2\\uffd3\\uffd4\\uffd5\\uffd6\\uffd7" |
241 | + "\\uffda\\uffdb\\uffdc" |
242 | ), |
243 | sb.toString()); |
244 | } |
245 | |
246 | |
247 | public final void testMinimalEscapeRegex() { |
248 | StringBuilder sb = new StringBuilder(); |
249 | Escaping.escapeRegex(CHARS, false, false, sb); |
250 | assertStringsEqual( |
251 | ("\\x00\001\002\003\004\005\006\007\\b\t\\n\013\014\\r\016\017" |
252 | + "\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" |
253 | // quotes unescaped. regex specials escaped |
254 | + " !\"#\\$%&'\\(\\)\\*\\+,-\\.\\/" |
255 | + "0123456789:;<=>\\?" |
256 | + "@ABCDEFGHIJKLMNO" |
257 | + "PQRSTUVWXYZ\\[\\\\\\]\\^_" |
258 | + "`abcdefghijklmno" |
259 | + "pqrstuvwxyz\\{\\|\\}~\177" |
260 | + "\u0080\u0081\u0082\u0083\u0084" |
261 | + "\\u200e\\u200f\u2010\\u2028\\u2029" |
262 | + "\ud834\udd20" |
263 | + "\\ud834\\udd77" |
264 | ), |
265 | sb.toString()); |
266 | } |
267 | |
268 | public final void testParanoidEscapeRegex() { |
269 | StringBuilder sb = new StringBuilder(); |
270 | Escaping.escapeRegex(CHARS, false, true, sb); |
271 | assertStringsEqual( |
272 | (// all ctrl chars escaped |
273 | "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07" |
274 | + "\\b\\t\\n\\x0b\\f\\r\\x0e\\x0f" |
275 | + "\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17" |
276 | + "\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f" |
277 | + " !\"#\\$%\\x26'\\(\\)\\*\\+,-\\.\\/" |
278 | + "0123456789:;\\x3c=\\x3e\\?" |
279 | + "@ABCDEFGHIJKLMNO" |
280 | + "PQRSTUVWXYZ\\[\\\\\\]\\^_" |
281 | + "`abcdefghijklmno" |
282 | + "pqrstuvwxyz\\{\\|\\}~\177" |
283 | + "\u0080\u0081\u0082\u0083\u0084" |
284 | + "\\u200e\\u200f\u2010\\u2028\\u2029" |
285 | + "\ud834\udd20" |
286 | + "\\ud834\\udd77" |
287 | ), |
288 | sb.toString()); |
289 | } |
290 | |
291 | public final void testRegexNormalization() { |
292 | StringBuilder sb = new StringBuilder(); |
293 | Escaping.normalizeRegex( |
294 | "<Foo+\\> \u2028 \\\\Ba*r \r Baz\\+\\+", false, true, sb); |
295 | assertStringsEqual( |
296 | "\\x3cFoo+\\x3e \\u2028 \\\\Ba*r \\r Baz\\+\\+", sb.toString()); |
297 | } |
298 | |
299 | public final void testRegexNormalizationBalancesCharGroups() { |
300 | // Make sure that the normalized regex always has balanced [...] blocks |
301 | // since / in those are not considered as closing the token. |
302 | { |
303 | StringBuilder sb = new StringBuilder(); |
304 | Escaping.normalizeRegex("[", false, true, sb); |
305 | assertStringsEqual("\\[", sb.toString()); |
306 | } |
307 | |
308 | { |
309 | StringBuilder sb = new StringBuilder(); |
310 | Escaping.normalizeRegex("[a-z][foo", false, true, sb); |
311 | assertStringsEqual("[a-z]\\[foo", sb.toString()); |
312 | } |
313 | |
314 | { |
315 | StringBuilder sb = new StringBuilder(); |
316 | Escaping.normalizeRegex("[a-z][[foo]", false, true, sb); |
317 | assertStringsEqual("[a-z][\\[foo]", sb.toString()); |
318 | } |
319 | |
320 | { |
321 | StringBuilder sb = new StringBuilder(); |
322 | Escaping.normalizeRegex("[a-z][[foo", false, true, sb); |
323 | assertStringsEqual("[a-z]\\[\\[foo", sb.toString()); |
324 | } |
325 | |
326 | { |
327 | StringBuilder sb = new StringBuilder(); |
328 | Escaping.normalizeRegex("[a-z][[foo[", false, true, sb); |
329 | assertStringsEqual("[a-z]\\[\\[foo\\[", sb.toString()); |
330 | } |
331 | } |
332 | |
333 | public final void testEscapeXml() { |
334 | StringBuilder sb = new StringBuilder(); |
335 | Escaping.escapeXml(CHARS, false, sb); |
336 | assertStringsEqual( |
337 | (// all ctrl chars escaped |
338 | "�" |
339 | + "\t\n\r" |
340 | + "" |
341 | + "" |
342 | + " !"#$%&'()*+,-./" |
343 | + "0123456789:;<=>?" |
344 | + "@ABCDEFGHIJKLMNO" |
345 | + "PQRSTUVWXYZ[\\]^_" |
346 | + "`abcdefghijklmno" |
347 | + "pqrstuvwxyz{|}~" |
348 | + "€‚ƒ„" |
349 | + "\u200e\u200f\u2010\u2028\u2029" |
350 | + "\ud834\udd20" |
351 | + "\ud834\udd77" |
352 | ), |
353 | sb.toString()); |
354 | } |
355 | |
356 | public final void testEscapeXmlAsciiOnly() { |
357 | StringBuilder sb = new StringBuilder(); |
358 | Escaping.escapeXml(CHARS, true, sb); |
359 | assertStringsEqual( |
360 | (// all ctrl chars escaped |
361 | "�" |
362 | + "\t\n\r" |
363 | + "" |
364 | + "" |
365 | + " !"#$%&'()*+,-./" |
366 | + "0123456789:;<=>?" |
367 | + "@ABCDEFGHIJKLMNO" |
368 | + "PQRSTUVWXYZ[\\]^_" |
369 | + "`abcdefghijklmno" |
370 | + "pqrstuvwxyz{|}~" |
371 | + "€‚ƒ„" |
372 | + "‎‏‐

" |
373 | // Surrogate pairs are escaped by code-point, not code-unit. |
374 | + "𝄠" |
375 | + "𝅷" |
376 | ), |
377 | sb.toString()); |
378 | } |
379 | |
380 | public final void testEscapeCssString() { |
381 | StringBuilder sb; |
382 | |
383 | sb = new StringBuilder(); |
384 | Escaping.escapeCssString(CHARS, sb); |
385 | assertStringsEqual( |
386 | ("\\0\\1\\2\\3\\4\\5\\6\\7" |
387 | + "\\8 \\9 \\A\\B \\C \\D\\E\\F" |
388 | + "\\10\\11\\12\\13\\14\\15\\16\\17" |
389 | + "\\18\\19\\1A\\1B\\1C\\1D\\1E\\1F " |
390 | + " !\\22#$%&\\27\\28\\29\\2A\\2B\\2C-./" |
391 | + "0123456789:\\3B\\3C=\\3E?" |
392 | + "\\40 ABCDEFGHIJKLMNO" |
393 | + "PQRSTUVWXYZ\\5B\\5C\\5D^_" |
394 | + "`abcdefghijklmno" |
395 | + "pqrstuvwxyz\\7B\\7C\\7D~\\7F" |
396 | + "\\80\\81\\82\\83\\84" |
397 | + "\\200E\\200F\\2010\\2028\\2029" |
398 | + "\\D834\\DD20" |
399 | + "\\D834\\DD77 " |
400 | ), |
401 | sb.toString()); |
402 | |
403 | sb = new StringBuilder(); |
404 | Escaping.escapeCssString("<foo>", sb); |
405 | assertStringsEqual("\\3C foo\\3E ", sb.toString()); |
406 | |
407 | sb = new StringBuilder(); |
408 | Escaping.escapeCssString("<Bar>", sb); |
409 | assertStringsEqual("\\3C Bar\\3E ", sb.toString()); |
410 | |
411 | sb = new StringBuilder(); |
412 | Escaping.escapeCssString("<ZZZ>", sb); |
413 | assertStringsEqual("\\3CZZZ\\3E ", sb.toString()); |
414 | } |
415 | |
416 | public final void testEscapeCssIdent() { |
417 | StringBuilder sb; |
418 | |
419 | sb = new StringBuilder(); |
420 | Escaping.escapeCssIdent(CHARS, sb); |
421 | assertStringsEqual( |
422 | ("\\0\\1\\2\\3\\4\\5\\6\\7" |
423 | + "\\8 \\9 \\A\\B \\C \\D\\E\\F" |
424 | + "\\10\\11\\12\\13\\14\\15\\16\\17" |
425 | + "\\18\\19\\1A\\1B\\1C\\1D\\1E\\1F " |
426 | + "\\20\\21\\22\\23\\24\\25\\26\\27" |
427 | + "\\28\\29\\2A\\2B\\2C-\\2E\\2F " |
428 | + "0123456789\\3A\\3B\\3C\\3D\\3E\\3F" |
429 | + "\\40 ABCDEFGHIJKLMNO" |
430 | + "PQRSTUVWXYZ\\5B\\5C\\5D\\5E_" |
431 | + "\\60 abcdefghijklmno" |
432 | + "pqrstuvwxyz\\7B\\7C\\7D\\7E\\7F" |
433 | + "\\80\\81\\82\\83\\84" |
434 | + "\\200E\\200F\\2010\\2028\\2029" |
435 | + "\\D834\\DD20" |
436 | + "\\D834\\DD77 " |
437 | ), |
438 | sb.toString()); |
439 | |
440 | sb = new StringBuilder(); |
441 | Escaping.escapeCssIdent("foo-bar", sb); |
442 | assertStringsEqual("foo-bar", sb.toString()); |
443 | |
444 | sb = new StringBuilder(); |
445 | Escaping.escapeCssIdent("fo o", sb); |
446 | assertStringsEqual("fo\\20o", sb.toString()); |
447 | |
448 | sb = new StringBuilder(); |
449 | Escaping.escapeCssIdent("0foo", sb); |
450 | assertStringsEqual("\\30 foo", sb.toString()); |
451 | |
452 | sb = new StringBuilder(); |
453 | Escaping.escapeCssIdent("0zoicks", sb); |
454 | assertStringsEqual("\\30zoicks", sb.toString()); |
455 | |
456 | sb = new StringBuilder(); |
457 | Escaping.escapeCssIdent("4", sb); |
458 | assertStringsEqual("\\34 ", sb.toString()); |
459 | } |
460 | |
461 | public static void testEscapeUri() { |
462 | StringBuilder sb; |
463 | |
464 | sb = new StringBuilder(); |
465 | Escaping.escapeUri("", sb); |
466 | assertEquals("", sb.toString()); |
467 | |
468 | sb = new StringBuilder(); |
469 | Escaping.escapeUri("foo", sb); |
470 | assertEquals("foo", sb.toString()); |
471 | |
472 | sb = new StringBuilder(); |
473 | Escaping.escapeUri("FOO", sb); |
474 | assertEquals("FOO", sb.toString()); |
475 | |
476 | // All of the reserved characters must be encoded. |
477 | sb = new StringBuilder(); |
478 | Escaping.escapeUri(":/?#[]@!$&'()*+,;=", sb); |
479 | assertEquals( |
480 | "%3a%2f%3f%23%5b%5d%40%21%24%26%27%28%29%2a%2b%2c%3b%3d", |
481 | sb.toString()); |
482 | |
483 | sb = new StringBuilder(); |
484 | Escaping.escapeUri(CHARS, sb); |
485 | assertEquals( |
486 | "" |
487 | + "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" |
488 | + "%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" |
489 | // %20, not + since that is not valid in non-hierarchical URIs. |
490 | + "%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f" |
491 | + "0123456789%3a%3b%3c%3d%3e%3f" |
492 | + "%40ABCDEFGHIJKLMNO" |
493 | + "PQRSTUVWXYZ%5b%5c%5d%5e_" |
494 | + "%60abcdefghijklmno" |
495 | + "pqrstuvwxyz%7b%7c%7d~%7f" |
496 | + "%c2%80%c2%81%c2%82%c2%83%c2%84" |
497 | + "%e2%80%8e%e2%80%8f%e2%80%90%e2%80%a8%e2%80%a9" |
498 | + "%ed%a0%b4%ed%b4%a0%ed%a0%b4%ed%b5%b7", |
499 | sb.toString()); |
500 | } |
501 | |
502 | private static void assertStringsEqual(String a, String b) { |
503 | if (a.equals(b)) { return; } |
504 | int m = a.length(), n = b.length(); |
505 | int min = Math.min(m, n); |
506 | |
507 | int commonPrefix = 0; |
508 | while (commonPrefix < min |
509 | && a.charAt(commonPrefix) == b.charAt(commonPrefix)) { |
510 | ++commonPrefix; |
511 | } |
512 | |
513 | int commonSuffix = 0; |
514 | while (commonSuffix < (min - commonPrefix) |
515 | && a.charAt(m - commonSuffix - 1) == b.charAt(n - commonSuffix - 1) |
516 | ) { |
517 | ++commonSuffix; |
518 | } |
519 | |
520 | int msgPrefix = Math.max(0, commonPrefix - 2); |
521 | int msgSuffix = Math.max(0, commonSuffix - 2); |
522 | fail(diagnosticString(a, msgPrefix, m - msgSuffix) + " != " + |
523 | diagnosticString(b, msgPrefix, n - msgSuffix)); |
524 | } |
525 | |
526 | private static String diagnosticString(String s, int start, int end) { |
527 | StringBuilder sb = new StringBuilder(end - start + 16); |
528 | if (start != 0) { sb.append("..."); } |
529 | for (int i = start; i < end; ++i) { |
530 | char ch = s.charAt(i); |
531 | if (ch >= 0x20 && ch < 0x7f) { |
532 | sb.append(ch); |
533 | } else { |
534 | sb.append('{').append(Integer.toString(ch, 16)).append('}'); |
535 | } |
536 | } |
537 | if (end < s.length()) { sb.append("..."); } |
538 | return sb.toString(); |
539 | } |
540 | } |