1 | // Copyright (C) 2009 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.html; |
16 | |
17 | import com.google.caja.render.Concatenator; |
18 | import com.google.caja.reporting.MarkupRenderMode; |
19 | import com.google.caja.reporting.RenderContext; |
20 | import com.google.caja.util.CajaTestCase; |
21 | |
22 | import java.util.Arrays; |
23 | |
24 | import org.w3c.dom.DOMException; |
25 | import org.w3c.dom.Document; |
26 | import org.w3c.dom.DocumentFragment; |
27 | import org.w3c.dom.Element; |
28 | import org.w3c.dom.ProcessingInstruction; |
29 | |
30 | public class NodesTest extends CajaTestCase { |
31 | public final void testDecode() { |
32 | assertEquals(Nodes.decode("1 < 2 && 4 > "3""), |
33 | "1 < 2 && 4 > \"3\""); |
34 | assertEquals("", Nodes.decode("")); |
35 | assertEquals("No entities here", Nodes.decode("No entities here")); |
36 | assertEquals("No entities here & there", |
37 | Nodes.decode("No entities here & there")); |
38 | // Test that interrupted escapes and escapes at beginning and end of file |
39 | // are handled gracefully. |
40 | assertEquals("\\\\u000a", Nodes.decode("\\\\u000a")); |
41 | assertEquals("\n", Nodes.decode("
")); |
42 | assertEquals("\n", Nodes.decode("
")); |
43 | assertEquals("\n", Nodes.decode("
")); |
44 | assertEquals("\n", Nodes.decode("
")); |
45 | assertEquals("\n", Nodes.decode("
")); |
46 | assertEquals("\n", Nodes.decode("
")); |
47 | assertEquals(String.valueOf(Character.toChars(0x10000)), |
48 | Nodes.decode("𐀀")); |
49 | assertEquals("
", Nodes.decode("
")); |
50 | assertEquals("�ziggy", Nodes.decode("�ziggy")); |
51 | assertEquals("਀z;", Nodes.decode("਀z;")); |
52 | assertEquals("&#\n", Nodes.decode("&#
")); |
53 | assertEquals("&#x\n", Nodes.decode("&#x
")); |
54 | assertEquals("
\n", Nodes.decode("

")); |
55 | assertEquals("&#\n", Nodes.decode("&#
")); |
56 | assertEquals("&#x", Nodes.decode("&#x")); |
57 | assertEquals("�", Nodes.decode("�")); |
58 | assertEquals("&#", Nodes.decode("&#")); |
59 | |
60 | assertEquals("\\", Nodes.decode("\\")); |
61 | assertEquals("&", Nodes.decode("&")); |
62 | |
63 | assertEquals("�a;", Nodes.decode("�a;")); |
64 | assertEquals("\n", Nodes.decode(" ")); |
65 | assertEquals("\n", Nodes.decode("
")); |
66 | assertEquals("\n", Nodes.decode("
")); |
67 | assertEquals("\n", Nodes.decode("
")); |
68 | assertEquals("\n", Nodes.decode("
")); |
69 | assertEquals("\n", Nodes.decode("
")); |
70 | assertEquals("\t", Nodes.decode("	")); |
71 | |
72 | assertEquals("
", Nodes.decode("
")); |
73 | assertEquals("�ziggy", Nodes.decode("�ziggy")); |
74 | assertEquals("&#\n", Nodes.decode("&#
")); |
75 | assertEquals("�\n", Nodes.decode("�
")); |
76 | assertEquals("\n", Nodes.decode(" ")); |
77 | assertEquals("&#\n", Nodes.decode("&# ")); |
78 | assertEquals("", Nodes.decode("")); |
79 | assertEquals("
", Nodes.decode("
")); |
80 | |
81 | // test the named escapes |
82 | assertEquals("<", Nodes.decode("<")); |
83 | assertEquals(">", Nodes.decode(">")); |
84 | assertEquals("\"", Nodes.decode(""")); |
85 | assertEquals("'", Nodes.decode("'")); |
86 | assertEquals("&", Nodes.decode("&")); |
87 | assertEquals("<", Nodes.decode("&lt;")); |
88 | assertEquals("&", Nodes.decode("&")); |
89 | assertEquals("&", Nodes.decode("&")); |
90 | assertEquals("&", Nodes.decode("&AmP;")); |
91 | assertEquals("\u0391", Nodes.decode("Α")); |
92 | assertEquals("\u03b1", Nodes.decode("α")); |
93 | |
94 | assertEquals("&;", Nodes.decode("&;")); |
95 | assertEquals("&bogus;", Nodes.decode("&bogus;")); |
96 | } |
97 | |
98 | public final void testRenderOfEmbeddedXml() throws Exception { |
99 | assertEquals( |
100 | "<td width=\"10\"><svg:Rect width=\"50\"></svg:Rect></td>", |
101 | Nodes.render( |
102 | xmlFragment(fromString( |
103 | "<html:td width='10'><svg:Rect width='50'/></html:td>")), |
104 | MarkupRenderMode.HTML)); |
105 | assertEquals( |
106 | "<td width=\"10\"><svg:Rect width=\"50\" /></td>", |
107 | Nodes.render( |
108 | xmlFragment(fromString( |
109 | "<html:td width='10'><svg:Rect width='50'/></html:td>")), |
110 | MarkupRenderMode.XML)); |
111 | } |
112 | |
113 | public final void testRenderWithNonstandardNamespaces() throws Exception { |
114 | assertEquals( |
115 | "<td width=\"10\"><svg:Rect width=\"50\" /></td>", |
116 | Nodes.render(xmlFragment(fromString( |
117 | "" |
118 | + "<html:td width='10' xmlns:s='http://www.w3.org/2000/svg'>" |
119 | + "<s:Rect width='50'/>" |
120 | + "</html:td>")), |
121 | MarkupRenderMode.XML)); |
122 | } |
123 | |
124 | public final void testRenderWithUnknownNamespace() throws Exception { |
125 | assertEquals( |
126 | "" |
127 | + "<xml:foo>" |
128 | + "<_ns1:baz xmlns:_ns1=\"http://bobs.house.of/XML&BBQ\"" |
129 | + " boo=\"howdy\" xml:lang=\"es\" />" |
130 | + "</xml:foo>", |
131 | Nodes.render(xmlFragment(fromString( |
132 | "" |
133 | + "<foo xmlns='http://www.w3.org/XML/1998/namespace'" |
134 | + " xmlns:bar='http://bobs.house.of/XML&BBQ'>" |
135 | + "<bar:baz boo='howdy' xml:lang='es'/>" |
136 | + "</foo>")), |
137 | MarkupRenderMode.XML)); |
138 | } |
139 | |
140 | public final void testRenderWithMaskedInputNamespace1() throws Exception { |
141 | DocumentFragment fragment = xmlFragment(fromString( |
142 | "<svg:foo><svg:bar xmlns:svg='" + Namespaces.HTML_NAMESPACE_URI |
143 | + "'/></svg:foo>")); |
144 | assertEquals("<svg:foo><bar></bar></svg:foo>", Nodes.render(fragment)); |
145 | } |
146 | |
147 | public final void testRenderWithMaskedInputNamespace2() throws Exception { |
148 | DocumentFragment fragment = xmlFragment(fromString( |
149 | "<svg:foo><svg:bar xmlns:svg='http://foo/'/></svg:foo>")); |
150 | assertEquals( |
151 | "<svg:foo><_ns1:bar xmlns:_ns1=\"http://foo/\"></_ns1:bar></svg:foo>", |
152 | Nodes.render(fragment)); |
153 | } |
154 | |
155 | public final void testRenderWithMaskedOutputNamespace1() throws Exception { |
156 | DocumentFragment fragment = xmlFragment(fromString( |
157 | "<svg:foo><xml:bar/></svg:foo>")); |
158 | Namespaces ns = new Namespaces( |
159 | Namespaces.HTML_DEFAULT, "svg", Namespaces.XML_NAMESPACE_URI); |
160 | StringBuilder sb = new StringBuilder(); |
161 | RenderContext rc = new RenderContext(new Concatenator(sb)) |
162 | .withMarkupRenderMode(MarkupRenderMode.XML); |
163 | Nodes.render(fragment, ns, rc); |
164 | rc.getOut().noMoreTokens(); |
165 | assertEquals( |
166 | "" |
167 | + "<_ns2:foo xmlns:_ns2=\"http://www.w3.org/2000/svg\">" |
168 | + "<svg:bar /></_ns2:foo>", |
169 | sb.toString()); |
170 | } |
171 | |
172 | public final void testHtmlNamesNormalized() throws Exception { |
173 | Document doc = DomParser.makeDocument(null, null); |
174 | Element el = doc.createElementNS(Namespaces.HTML_NAMESPACE_URI, "SPAN"); |
175 | el.setAttributeNS(Namespaces.HTML_NAMESPACE_URI, "TITLE", "Howdy"); |
176 | |
177 | assertEquals("<span title=\"Howdy\"></span>", Nodes.render(el)); |
178 | |
179 | Namespaces ns = new Namespaces( |
180 | Namespaces.HTML_DEFAULT, "html", Namespaces.HTML_NAMESPACE_URI); |
181 | StringBuilder sb = new StringBuilder(); |
182 | RenderContext rc = new RenderContext(new Concatenator(sb)) |
183 | .withMarkupRenderMode(MarkupRenderMode.HTML); |
184 | Nodes.render(el, ns, rc); |
185 | rc.getOut().noMoreTokens(); |
186 | assertEquals("<html:span title=\"Howdy\"></html:span>", sb.toString()); |
187 | } |
188 | |
189 | public final void testNoSneakyNamespaceDecls1() throws Exception { |
190 | Document doc = DomParser.makeDocument(null, null); |
191 | Element el = doc.createElementNS(Namespaces.SVG_NAMESPACE_URI, "span"); |
192 | try { |
193 | el.setAttributeNS( |
194 | Namespaces.SVG_NAMESPACE_URI, "xmlns", Namespaces.HTML_NAMESPACE_URI); |
195 | } catch (Exception ex) { |
196 | try { |
197 | el.setAttributeNS( |
198 | Namespaces.HTML_NAMESPACE_URI, "xmlns", |
199 | Namespaces.HTML_NAMESPACE_URI); |
200 | } catch (Exception ex2) { |
201 | el.setAttribute("xmlns", Namespaces.HTML_NAMESPACE_URI); |
202 | } |
203 | } |
204 | el.appendChild(doc.createElementNS(Namespaces.SVG_NAMESPACE_URI, "br")); |
205 | String rendered; |
206 | try { |
207 | rendered = Nodes.render(el, MarkupRenderMode.XML); |
208 | } catch (RuntimeException ex) { |
209 | return; // Failure is an option. |
210 | } |
211 | assertEquals("<svg:span><svg:br /></svg:span>", rendered); |
212 | } |
213 | |
214 | public final void testNoSneakyNamespaceDecls2() throws Exception { |
215 | Document doc = DomParser.makeDocument(null, null); |
216 | Element el = doc.createElementNS(Namespaces.SVG_NAMESPACE_URI, "span"); |
217 | try { |
218 | el.setAttributeNS( |
219 | Namespaces.XMLNS_NAMESPACE_URI, "svg", Namespaces.HTML_NAMESPACE_URI); |
220 | } catch (Exception ex) { |
221 | try { |
222 | el.setAttributeNS( |
223 | Namespaces.HTML_NAMESPACE_URI, "xmlns:svg", |
224 | Namespaces.HTML_NAMESPACE_URI); |
225 | } catch (Exception ex2) { |
226 | el.setAttribute("xmlns:svg", Namespaces.HTML_NAMESPACE_URI); |
227 | } |
228 | } |
229 | el.appendChild(doc.createElementNS(Namespaces.SVG_NAMESPACE_URI, "br")); |
230 | String rendered; |
231 | try { |
232 | rendered = Nodes.render(el, MarkupRenderMode.XML); |
233 | } catch (RuntimeException ex) { |
234 | return; // Failure is an option. |
235 | } |
236 | assertEquals("<svg:span><svg:br /></svg:span>", rendered); |
237 | } |
238 | |
239 | public final void testNoSneakyNamespaceDecls3() throws Exception { |
240 | Document doc = DomParser.makeDocument(null, null); |
241 | Element el = doc.createElementNS( |
242 | Namespaces.SVG_NAMESPACE_URI, "span"); |
243 | // Override a definition used elsewhere. |
244 | el.setAttribute("xmlns:svg", Namespaces.HTML_NAMESPACE_URI); |
245 | |
246 | el.appendChild(doc.createElementNS(Namespaces.SVG_NAMESPACE_URI, "br")); |
247 | String rendered; |
248 | try { |
249 | rendered = Nodes.render(el, MarkupRenderMode.XML); |
250 | } catch (RuntimeException ex) { |
251 | return; // Failure is an option. |
252 | } |
253 | assertEquals("<svg:span><svg:br /></svg:span>", rendered); |
254 | } |
255 | |
256 | public final void testProcessingInstructions() { |
257 | Document doc = DomParser.makeDocument(null, null); |
258 | ProcessingInstruction pi = doc.createProcessingInstruction("foo", "bar"); |
259 | assertEquals("<?foo bar?>", Nodes.render(pi, MarkupRenderMode.XML)); |
260 | } |
261 | |
262 | public final void testBadProcessingInstructions() { |
263 | Document doc = DomParser.makeDocument(null, null); |
264 | for (String[] badPi : new String[][] { |
265 | { "xml", "foo" }, { "XmL", "foo" }, |
266 | { "foo?><script>alert(1)</script>", "<?bar baz" }, |
267 | { "ok", "foo?><script>alert(1)</script><?foo bar" }, |
268 | }) { |
269 | try { |
270 | ProcessingInstruction pi = doc.createProcessingInstruction( |
271 | badPi[0], badPi[1]); |
272 | Nodes.render(pi, MarkupRenderMode.XML); |
273 | } catch (IllegalStateException ex) { |
274 | continue; // OK |
275 | } catch (DOMException ex) { |
276 | continue; // OK |
277 | } |
278 | fail("Rendered " + Arrays.toString(badPi)); |
279 | } |
280 | } |
281 | |
282 | public final void testProcessingInstructionInHtml() { |
283 | Document doc = DomParser.makeDocument(null, null); |
284 | ProcessingInstruction pi = doc.createProcessingInstruction( |
285 | "foo", "<script>alert(1)</script>"); |
286 | Element el = doc.createElementNS(Namespaces.HTML_NAMESPACE_URI, "div"); |
287 | el.appendChild(pi); |
288 | assertEquals( |
289 | "<div><?foo <script>alert(1)</script>?></div>", |
290 | Nodes.render(el, MarkupRenderMode.XML)); |
291 | try { |
292 | Nodes.render(el, MarkupRenderMode.HTML); |
293 | } catch (IllegalStateException ex) { |
294 | // OK |
295 | return; |
296 | } |
297 | fail("Rendered in html"); |
298 | } |
299 | |
300 | public final void testRenderWithBrokenNekoDom() throws Exception { |
301 | Element el = html(fromString("<a href='foo.html'>bar</a>")); |
302 | assertEquals( |
303 | "<html><head></head><body><a href=\"foo.html\">bar</a></body></html>", |
304 | Nodes.render(new NullLocalNameMembrane().wrap(el, Element.class))); |
305 | } |
306 | |
307 | public final void testRenderModes() throws Exception { |
308 | DocumentFragment f = htmlFragment(fromString( |
309 | "<input checked name=foo type=checkbox>")); |
310 | assertEquals( |
311 | "<input checked=\"checked\" name=\"foo\" type=\"checkbox\" />", |
312 | Nodes.render(f, MarkupRenderMode.XML)); |
313 | assertEquals( |
314 | "<input checked=\"checked\" name=\"foo\" type=\"checkbox\" />", |
315 | Nodes.render(f, MarkupRenderMode.HTML)); |
316 | assertEquals( |
317 | "<input checked name=\"foo\" type=\"checkbox\">", |
318 | Nodes.render(f, MarkupRenderMode.HTML4_BACKWARDS_COMPAT)); |
319 | } |
320 | |
321 | |
322 | public final void testRenderSpeed() throws Exception { |
323 | Element doc = html(fromResource("amazon.com.html")); |
324 | benchmark(100, doc); // prime the JIT |
325 | Thread.sleep(250); // Let the JIT kick-in. |
326 | int microsPerRun = benchmark(250, doc); |
327 | // See extractVarZ in "tools/dashboard/dashboard.pl". |
328 | System.out.println( |
329 | " VarZ:" + getClass().getName() + ".msPerRun=" + microsPerRun); |
330 | } |
331 | |
332 | private int benchmark(int nRuns, Element el) { |
333 | long t0 = System.nanoTime(); |
334 | for (int i = nRuns; --i >= 0;) { Nodes.render(el); } |
335 | return (int) ((((double) (System.nanoTime() - t0)) / nRuns) / 1e3); |
336 | } |
337 | } |