1 | // Copyright (C) 2005 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.parser.js; |
16 | |
17 | import com.google.caja.util.CajaTestCase; |
18 | |
19 | import java.util.Random; |
20 | |
21 | /** |
22 | * |
23 | * @author mikesamuel@gmail.com |
24 | */ |
25 | public class StringLiteralTest extends CajaTestCase { |
26 | public final void testUnquotedValue() { |
27 | assertEquals("", StringLiteral.getUnquotedValueOf("")); |
28 | assertEquals("foo", StringLiteral.getUnquotedValueOf("foo")); |
29 | assertEquals("foo\\bar", StringLiteral.getUnquotedValueOf("foo\\bar")); |
30 | assertEquals("", StringLiteral.getUnquotedValueOf("''")); |
31 | assertEquals("\"\"", StringLiteral.getUnquotedValueOf("'\"\"'")); |
32 | assertEquals("\"\"", StringLiteral.getUnquotedValueOf("'\\\"\\\"'")); |
33 | assertEquals("foo\bar", StringLiteral.getUnquotedValueOf("'foo\\bar'")); |
34 | assertEquals("foo\nbar", StringLiteral.getUnquotedValueOf("'foo\\nbar'")); |
35 | assertEquals("foo\\bar\\baz", |
36 | StringLiteral.getUnquotedValueOf("'foo\\\\bar\\\\baz'")); |
37 | assertEquals( |
38 | "foo bar", StringLiteral.getUnquotedValueOf("'foo\\u0020bar'")); |
39 | assertEquals("foo bar", StringLiteral.getUnquotedValueOf("'foo\\040bar'")); |
40 | assertEquals("foo bar", StringLiteral.getUnquotedValueOf("'foo\\40bar'")); |
41 | assertEquals("foo\0bar", StringLiteral.getUnquotedValueOf("'foo\\0bar'")); |
42 | assertEquals("foo\0bar", StringLiteral.getUnquotedValueOf("'foo\\00bar'")); |
43 | assertEquals("foo\0" + "0bar", |
44 | StringLiteral.getUnquotedValueOf("'foo\\0000bar'")); |
45 | assertEquals("foo8bar", StringLiteral.getUnquotedValueOf("'foo\\8bar'")); |
46 | assertEquals("\n3", StringLiteral.getUnquotedValueOf("'\\0123'")); |
47 | assertEquals( |
48 | "\u0123" + "4", StringLiteral.getUnquotedValueOf("'\\u01234'")); |
49 | assertEquals( |
50 | "\u0123" + "a", StringLiteral.getUnquotedValueOf("'\\u0123a'")); |
51 | assertEquals( |
52 | "\u0123" + "A", StringLiteral.getUnquotedValueOf("'\\u0123A'")); |
53 | assertEquals( |
54 | "\u0123" + "f", StringLiteral.getUnquotedValueOf("'\\u0123f'")); |
55 | assertEquals( |
56 | "\u0123" + "F", StringLiteral.getUnquotedValueOf("'\\u0123F'")); |
57 | assertEquals("'", StringLiteral.getUnquotedValueOf("'\\u0027'")); |
58 | assertEquals("\"", StringLiteral.getUnquotedValueOf("'\\u0022'")); |
59 | assertEquals("'", StringLiteral.getUnquotedValueOf("\"\\u0027\"")); |
60 | assertEquals("\"", StringLiteral.getUnquotedValueOf("\"\\u0022\"")); |
61 | assertEquals("@", StringLiteral.getUnquotedValueOf("'\\x40'")); |
62 | assertEquals("x4", StringLiteral.getUnquotedValueOf("'\\x4'")); |
63 | } |
64 | |
65 | public final void testQuoteValue() { |
66 | assertEquals("''", StringLiteral.toQuotedValue("")); |
67 | assertEquals("'foo'", StringLiteral.toQuotedValue("foo")); |
68 | assertEquals("'foo\\bar'", StringLiteral.toQuotedValue("foo\bar")); |
69 | assertEquals("'foo\\nbar'", StringLiteral.toQuotedValue("foo\nbar")); |
70 | assertEquals( |
71 | "'foo\\\\bar\\\\baz'", StringLiteral.toQuotedValue("foo\\bar\\baz")); |
72 | assertEquals("'foo bar'", StringLiteral.toQuotedValue("foo bar")); |
73 | assertEquals("'foo\\x00bar'", StringLiteral.toQuotedValue("foo\0bar")); |
74 | assertEquals("'foo\\x7fbar'", StringLiteral.toQuotedValue("foo\u007fbar")); |
75 | assertEquals( |
76 | "'foo\\uabcdbar'", StringLiteral.toQuotedValue("foo\uabcdbar")); |
77 | assertEquals("'\\'foo\\''", StringLiteral.toQuotedValue("'foo'")); |
78 | assertEquals("'\\\"foo\\\"'", StringLiteral.toQuotedValue("\"foo\"")); |
79 | assertEquals("'\\\"foo\\\\\\\"'", StringLiteral.toQuotedValue("\"foo\\\"")); |
80 | } |
81 | |
82 | public final void testQuotingAndUnquotingAreComplements() { |
83 | Random rnd = new Random(SEED); |
84 | for (int i = 2000; --i >= 0;) { |
85 | String s = makeRandomString(rnd); |
86 | assertEquals( |
87 | s, StringLiteral.getUnquotedValueOf(StringLiteral.toQuotedValue(s))); |
88 | } |
89 | } |
90 | |
91 | public final void testRandomStringsParseable() { |
92 | Random rnd = new Random(SEED); |
93 | for (int i = 2000; --i >= 0;) { |
94 | String literal = makeRandomQuotedString(rnd); |
95 | // Test that it parses. |
96 | String value = StringLiteral.getUnquotedValueOf(literal); |
97 | String requoted = StringLiteral.toQuotedValue(value); |
98 | // Now make sure that our unquoting works |
99 | assertEquals(value, StringLiteral.getUnquotedValueOf(requoted)); |
100 | // Make sure that the requoted string is coherent. |
101 | char delimiter = requoted.charAt(0); |
102 | assertTrue(requoted.length() >= 2); |
103 | assertEquals(delimiter, requoted.charAt(requoted.length() - 1)); |
104 | if ("'\"".indexOf(delimiter) < 0) { |
105 | fail("delimiter 0x" + Integer.toString(delimiter, 16)); |
106 | } |
107 | for (int ci = 1, end = requoted.length() - 1; ci < end; ++ci) { |
108 | char ch = requoted.charAt(ci); |
109 | if (ch == '\\') { |
110 | ++ci; |
111 | assertTrue("close delimiter is escaped", ci < end - 1); |
112 | } |
113 | assertTrue("delimiter appears unescaped", ch != delimiter); |
114 | if ("\r\n\u2028\u2029".indexOf(ch) >= 0) { |
115 | fail("newline 0x" + Integer.toString(ch, 16)); |
116 | } |
117 | } |
118 | } |
119 | } |
120 | |
121 | private static String makeRandomString(Random rnd) { |
122 | int len = (int) Math.min(Math.abs(rnd.nextGaussian() * 128), 1024); |
123 | StringBuilder sb = new StringBuilder(len); |
124 | while (--len >= 0) { |
125 | sb.appendCodePoint(randomCodePoint(rnd)); |
126 | } |
127 | return sb.toString(); |
128 | } |
129 | |
130 | private static String makeRandomQuotedString(Random rnd) { |
131 | int len = (int) Math.min(Math.abs(rnd.nextGaussian() * 128), 1024); |
132 | int delim = rnd.nextInt(2) == 0 ? '"' : '\''; |
133 | StringBuilder sb = new StringBuilder(len); |
134 | sb.append(delim); |
135 | while (--len >= 0) { |
136 | char ch = randomChar(rnd); |
137 | boolean escape; |
138 | String sequence = null; |
139 | switch (ch) { |
140 | case '\u2028': |
141 | escape = true; |
142 | sequence = "\\u2028"; |
143 | break; |
144 | case '\u2029': |
145 | escape = true; |
146 | sequence = "\\u2029"; |
147 | break; |
148 | case '\r': |
149 | escape = true; |
150 | sequence = "\\r"; |
151 | break; |
152 | case '\n': |
153 | escape = true; |
154 | sequence = "\\n"; |
155 | break; |
156 | default: |
157 | escape = ch == 0 || ch == delim; |
158 | break; |
159 | } |
160 | if (!escape) { |
161 | int escapeType = rnd.nextInt(16); |
162 | switch (escapeType) { |
163 | case 0: |
164 | if (ch < 0377) { |
165 | sequence = escapeSequence(ch, 8, rnd.nextInt(3)); |
166 | } else { |
167 | sequence = "u" + escapeSequence(ch, 16, 4); |
168 | } |
169 | break; |
170 | case 1: |
171 | if (ch < 0x100) { |
172 | sequence = escapeSequence(ch, 16, 2); |
173 | } else { |
174 | sequence = "u" + escapeSequence(ch, 16, 4); |
175 | } |
176 | break; |
177 | case 2: |
178 | sequence = "u" + escapeSequence(ch, 16, 4); |
179 | break; |
180 | } |
181 | escape = sequence != null; |
182 | } |
183 | if (escape) { |
184 | sb.append('\\'); |
185 | } |
186 | if (sequence == null) { |
187 | sb.append(ch); |
188 | } else { |
189 | sb.append(sequence); |
190 | } |
191 | } |
192 | sb.append(delim); |
193 | return sb.toString(); |
194 | } |
195 | |
196 | private static int randomCodePoint(Random rnd) { |
197 | int mag; |
198 | switch (rnd.nextInt(4)) { |
199 | default: |
200 | mag = 1 << 8; |
201 | break; |
202 | case 2: |
203 | mag = 1 << 16; |
204 | break; |
205 | case 3: |
206 | mag = 0x10ffff; |
207 | break; |
208 | } |
209 | int codePoint = rnd.nextInt(mag); |
210 | if (0xd800 <= codePoint && codePoint <= 0xdfff) { // surrogates |
211 | codePoint = codePoint & 0xff; |
212 | } |
213 | return codePoint; |
214 | } |
215 | |
216 | private static char randomChar(Random rnd) { |
217 | char ch = (char) rnd.nextInt(Character.MAX_VALUE - Character.MIN_VALUE + 1); |
218 | if (0xd800 <= ch && ch <= 0xdfff) { // surrogates |
219 | ch = (char) (ch & 0xff); |
220 | } |
221 | return ch; |
222 | } |
223 | |
224 | /** |
225 | * Generates the numeric portion of an octal, hex, or unicode escape sequence. |
226 | * @param ch the character to escape |
227 | * @param radix 8 for octal, 16 for hex or unicode. |
228 | * @param nDigits the minimum number of digits. 0's will be added to the left |
229 | * to pad the value to at least nDigits. |
230 | */ |
231 | private static String escapeSequence(char ch, int radix, int nDigits) { |
232 | StringBuilder sb = new StringBuilder(nDigits); |
233 | sb.append(Integer.toString(ch, radix)); |
234 | while (sb.length() < nDigits) { |
235 | sb.insert(0, '0'); |
236 | } |
237 | return sb.toString(); |
238 | } |
239 | } |