Security update for Core, with self-updated composer
[yaffs-website] / vendor / masterminds / html5 / test / HTML5 / Serializer / OutputRulesTest.php
1 <?php
2 namespace Masterminds\HTML5\Tests\Serializer;
3
4 use Masterminds\HTML5\Serializer\OutputRules;
5 use Masterminds\HTML5\Serializer\Traverser;
6 use Masterminds\HTML5;
7
8 class OutputRulesTest extends \Masterminds\HTML5\Tests\TestCase
9 {
10
11     protected $markup = '<!doctype html>
12     <html lang="en">
13       <head>
14         <meta charset="utf-8">
15         <title>Test</title>
16       </head>
17       <body>
18         <p>This is a test.</p>
19       </body>
20     </html>';
21
22     /**
23      * @var HTML5
24      */
25     protected $html5;
26
27     public function setUp()
28     {
29         $this->html5 = $this->getInstance();
30     }
31
32     /**
33      * Using reflection we make a protected method accessible for testing.
34      *
35      * @param string $name
36      *            The name of the method on the Traverser class to test.
37      *
38      * @return \ReflectionMethod for the specified method
39      */
40     public function getProtectedMethod($name)
41     {
42         $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\OutputRules');
43         $method = $class->getMethod($name);
44         $method->setAccessible(true);
45
46         return $method;
47     }
48
49     public function getTraverserProtectedProperty($name)
50     {
51         $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\Traverser');
52         $property = $class->getProperty($name);
53         $property->setAccessible(true);
54
55         return $property;
56     }
57
58     public function getOutputRules($options = array())
59     {
60         $options = $options + $this->html5->getOptions();
61         $stream = fopen('php://temp', 'w');
62         $dom = $this->html5->loadHTML($this->markup);
63         $r = new OutputRules($stream, $options);
64         $t = new Traverser($dom, $stream, $r, $options);
65
66         return array(
67             $r,
68             $stream
69         );
70     }
71
72     public function testDocument()
73     {
74         $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>foo</body></html>');
75
76         $stream = fopen('php://temp', 'w');
77         $r = new OutputRules($stream, $this->html5->getOptions());
78         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
79
80         $r->document($dom);
81         $expected = '<!DOCTYPE html>' . PHP_EOL . '<html lang="en"><body>foo</body></html>' . PHP_EOL;
82         $this->assertEquals($expected, stream_get_contents($stream, - 1, 0));
83     }
84
85     public function testEmptyDocument()
86     {
87         $dom = $this->html5->loadHTML('');
88
89         $stream = fopen('php://temp', 'w');
90         $r = new OutputRules($stream, $this->html5->getOptions());
91         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
92
93         $r->document($dom);
94         $expected = '<!DOCTYPE html>' . PHP_EOL;
95         $this->assertEquals($expected, stream_get_contents($stream, - 1, 0));
96     }
97
98     public function testDoctype()
99     {
100         $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>foo</body></html>');
101
102         $stream = fopen('php://temp', 'w');
103         $r = new OutputRules($stream, $this->html5->getOptions());
104         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
105
106         $m = $this->getProtectedMethod('doctype');
107         $m->invoke($r, 'foo');
108         $this->assertEquals("<!DOCTYPE html>" . PHP_EOL, stream_get_contents($stream, - 1, 0));
109     }
110
111     public function testElement()
112     {
113         $dom = $this->html5->loadHTML(
114             '<!doctype html>
115     <html lang="en">
116       <body>
117         <div id="foo" class="bar baz">foo bar baz</div>
118         <svg width="150" height="100" viewBox="0 0 3 2">
119           <rect width="1" height="2" x="0" fill="#008d46" />
120           <rect width="1" height="2" x="1" fill="#ffffff" />
121           <rect width="1" height="2" x="2" fill="#d2232c" />
122         </svg>
123       </body>
124     </html>');
125
126         $stream = fopen('php://temp', 'w');
127         $r = new OutputRules($stream, $this->html5->getOptions());
128         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
129
130         $list = $dom->getElementsByTagName('div');
131         $r->element($list->item(0));
132         $this->assertEquals('<div id="foo" class="bar baz">foo bar baz</div>', stream_get_contents($stream, - 1, 0));
133     }
134
135     function testSerializeWithNamespaces()
136     {
137         $this->html5 = $this->getInstance(array(
138             'xmlNamespaces' => true
139         ));
140
141         $source = '
142             <!DOCTYPE html>
143             <html><body id="body" xmlns:x="http://www.prefixed.com">
144                     <a id="bar1" xmlns="http://www.prefixed.com/bar1">
145                         <b id="bar4" xmlns="http://www.prefixed.com/bar4"><x:prefixed id="prefixed">xy</x:prefixed></b>
146                     </a>
147                     <svg id="svg">svg</svg>
148                     <c id="bar2" xmlns="http://www.prefixed.com/bar2"></c>
149                     <div id="div"></div>
150                     <d id="bar3"></d>
151                     <xn:d id="bar5" xmlns:xn="http://www.prefixed.com/xn" xmlns="http://www.prefixed.com/bar5_x"><x id="bar5_x">y</x></xn:d>
152                 </body>
153             </html>';
154
155         $dom = $this->html5->loadHTML($source, array(
156             'xmlNamespaces' => true
157         ));
158         $this->assertFalse($this->html5->hasErrors(), print_r($this->html5->getErrors(), 1));
159
160         $stream = fopen('php://temp', 'w');
161         $r = new OutputRules($stream, $this->html5->getOptions());
162         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
163
164         $t->walk();
165         $rendered = stream_get_contents($stream, - 1, 0);
166
167         $clear = function($s){
168             return trim(preg_replace('/[\s]+/', " ", $s));
169         };
170
171         $this->assertEquals($clear($source), $clear($rendered));
172     }
173
174     public function testElementWithScript()
175     {
176         $dom = $this->html5->loadHTML(
177             '<!doctype html>
178     <html lang="en">
179       <head>
180         <script>
181           var $jQ = jQuery.noConflict();
182           // Use jQuery via $jQ(...)
183           $jQ(document).ready(function () {
184             $jQ("#mktFrmSubmit").wrap("<div class=\'buttonSubmit\'></div>");
185             $jQ(".buttonSubmit").prepend("<span></span>");
186           });
187         </script>
188       </head>
189       <body>
190         <div id="foo" class="bar baz">foo bar baz</div>
191       </body>
192     </html>');
193
194         $stream = fopen('php://temp', 'w');
195         $r = new OutputRules($stream, $this->html5->getOptions());
196         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
197
198         $script = $dom->getElementsByTagName('script');
199         $r->element($script->item(0));
200         $this->assertEquals(
201             '<script>
202           var $jQ = jQuery.noConflict();
203           // Use jQuery via $jQ(...)
204           $jQ(document).ready(function () {
205             $jQ("#mktFrmSubmit").wrap("<div class=\'buttonSubmit\'></div>");
206             $jQ(".buttonSubmit").prepend("<span></span>");
207           });
208         </script>', stream_get_contents($stream, - 1, 0));
209     }
210
211     public function testElementWithStyle()
212     {
213         $dom = $this->html5->loadHTML(
214             '<!doctype html>
215     <html lang="en">
216       <head>
217         <style>
218           body > .bar {
219             display: none;
220           }
221         </style>
222       </head>
223       <body>
224         <div id="foo" class="bar baz">foo bar baz</div>
225       </body>
226     </html>');
227
228         $stream = fopen('php://temp', 'w');
229         $r = new OutputRules($stream, $this->html5->getOptions());
230         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
231
232         $style = $dom->getElementsByTagName('style');
233         $r->element($style->item(0));
234         $this->assertEquals('<style>
235           body > .bar {
236             display: none;
237           }
238         </style>', stream_get_contents($stream, - 1, 0));
239     }
240
241     public function testOpenTag()
242     {
243         $dom = $this->html5->loadHTML('<!doctype html>
244     <html lang="en">
245       <body>
246         <div id="foo" class="bar baz">foo bar baz</div>
247       </body>
248     </html>');
249
250         $stream = fopen('php://temp', 'w');
251         $r = new OutputRules($stream, $this->html5->getOptions());
252         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
253
254         $list = $dom->getElementsByTagName('div');
255         $m = $this->getProtectedMethod('openTag');
256         $m->invoke($r, $list->item(0));
257         $this->assertEquals('<div id="foo" class="bar baz">', stream_get_contents($stream, - 1, 0));
258     }
259
260     public function testCData()
261     {
262         $dom = $this->html5->loadHTML('<!doctype html>
263     <html lang="en">
264       <body>
265         <div><![CDATA[bar]]></div>
266       </body>
267     </html>');
268
269         $stream = fopen('php://temp', 'w');
270         $r = new OutputRules($stream, $this->html5->getOptions());
271         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
272
273         $list = $dom->getElementsByTagName('div');
274         $r->cdata($list->item(0)->childNodes->item(0));
275         $this->assertEquals('<![CDATA[bar]]>', stream_get_contents($stream, - 1, 0));
276
277         $dom = $this->html5->loadHTML('<!doctype html>
278     <html lang="en">
279       <body>
280         <div id="foo"></div>
281       </body>
282     </html>');
283
284         $dom->getElementById('foo')->appendChild(new \DOMCdataSection("]]>Foo<[![CDATA test ]]>"));
285
286         $stream = fopen('php://temp', 'w');
287         $r = new OutputRules($stream, $this->html5->getOptions());
288         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
289         $list = $dom->getElementsByTagName('div');
290         $r->cdata($list->item(0)->childNodes->item(0));
291
292         $this->assertEquals('<![CDATA[]]]]><![CDATA[>Foo<[![CDATA test ]]]]><![CDATA[>]]>', stream_get_contents($stream, - 1, 0));
293     }
294
295     public function testComment()
296     {
297         $dom = $this->html5->loadHTML('<!doctype html>
298     <html lang="en">
299       <body>
300         <div><!-- foo --></div>
301       </body>
302     </html>');
303
304         $stream = fopen('php://temp', 'w');
305         $r = new OutputRules($stream, $this->html5->getOptions());
306         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
307
308         $list = $dom->getElementsByTagName('div');
309         $r->comment($list->item(0)->childNodes->item(0));
310         $this->assertEquals('<!-- foo -->', stream_get_contents($stream, - 1, 0));
311
312         $dom = $this->html5->loadHTML('<!doctype html>
313     <html lang="en">
314       <body>
315         <div id="foo"></div>
316       </body>
317       </html>');
318         $dom->getElementById('foo')->appendChild(new \DOMComment('<!-- --> --> Foo -->'));
319
320         $stream = fopen('php://temp', 'w');
321         $r = new OutputRules($stream, $this->html5->getOptions());
322         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
323
324         $list = $dom->getElementsByTagName('div');
325         $r->comment($list->item(0)->childNodes->item(0));
326
327         // Could not find more definitive guidelines on what this should be. Went with
328         // what the HTML5 spec says and what \DOMDocument::saveXML() produces.
329         $this->assertEquals('<!--<!-- --> --> Foo -->-->', stream_get_contents($stream, - 1, 0));
330     }
331
332     public function testText()
333     {
334         $dom = $this->html5->loadHTML('<!doctype html>
335     <html lang="en">
336       <head>
337         <script>baz();</script>
338       </head>
339     </html>');
340
341         $stream = fopen('php://temp', 'w');
342         $r = new OutputRules($stream, $this->html5->getOptions());
343         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
344
345         $list = $dom->getElementsByTagName('script');
346         $r->text($list->item(0)->childNodes->item(0));
347         $this->assertEquals('baz();', stream_get_contents($stream, - 1, 0));
348
349         $dom = $this->html5->loadHTML('<!doctype html>
350     <html lang="en">
351       <head id="foo"></head>
352     </html>');
353         $foo = $dom->getElementById('foo');
354         $foo->appendChild(new \DOMText('<script>alert("hi");</script>'));
355
356         $stream = fopen('php://temp', 'w');
357         $r = new OutputRules($stream, $this->html5->getOptions());
358         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
359
360         $r->text($foo->firstChild);
361         $this->assertEquals('&lt;script&gt;alert("hi");&lt;/script&gt;', stream_get_contents($stream, - 1, 0));
362     }
363
364     public function testNl()
365     {
366         list ($o, $s) = $this->getOutputRules();
367
368         $m = $this->getProtectedMethod('nl');
369         $m->invoke($o);
370         $this->assertEquals(PHP_EOL, stream_get_contents($s, - 1, 0));
371     }
372
373     public function testWr()
374     {
375         list ($o, $s) = $this->getOutputRules();
376
377         $m = $this->getProtectedMethod('wr');
378         $m->invoke($o, 'foo');
379         $this->assertEquals('foo', stream_get_contents($s, - 1, 0));
380     }
381
382     public function getEncData()
383     {
384         return array(
385             array(
386                 false,
387                 '&\'<>"',
388                 '&amp;\'&lt;&gt;"',
389                 '&amp;&apos;&lt;&gt;&quot;'
390             ),
391             array(
392                 false,
393                 'This + is. a < test',
394                 'This + is. a &lt; test',
395                 'This &plus; is&period; a &lt; test'
396             ),
397             array(
398                 false,
399                 '.+#',
400                 '.+#',
401                 '&period;&plus;&num;'
402             ),
403
404             array(
405                 true,
406                 '.+#\'',
407                 '.+#\'',
408                 '&period;&plus;&num;&apos;'
409             ),
410             array(
411                 true,
412                 '&".<',
413                 '&amp;&quot;.<',
414                 '&amp;&quot;&period;&lt;'
415             ),
416             array(
417                 true,
418                 '&\'<>"',
419                 '&amp;\'<>&quot;',
420                 '&amp;&apos;&lt;&gt;&quot;'
421             ),
422             array(
423                 true,
424                 "\xc2\xa0\"'",
425                 '&nbsp;&quot;\'',
426                 '&nbsp;&quot;&apos;'
427             )
428         );
429     }
430
431     /**
432      * Test basic encoding of text.
433      * @dataProvider getEncData
434      */
435     public function testEnc($isAttribute, $test, $expected, $expectedEncoded)
436     {
437         list ($o, $s) = $this->getOutputRules();
438         $m = $this->getProtectedMethod('enc');
439
440         $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute));
441
442         list ($o, $s) = $this->getOutputRules(array(
443             'encode_entities' => true
444         ));
445         $m = $this->getProtectedMethod('enc');
446         $this->assertEquals($expectedEncoded, $m->invoke($o, $test, $isAttribute));
447     }
448
449     /**
450      * Test basic encoding of text.
451      * @dataProvider getEncData
452      */
453     public function testEscape($isAttribute, $test, $expected, $expectedEncoded)
454     {
455         list ($o, $s) = $this->getOutputRules();
456         $m = $this->getProtectedMethod('escape');
457
458         $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute));
459     }
460
461     public function booleanAttributes()
462     {
463         return array(
464             array('<img alt="" ismap>'),
465             array('<img alt="">'),
466             array('<input type="radio" readonly>'),
467             array('<input type="radio" checked disabled>'),
468             array('<input type="checkbox" checked disabled>'),
469             array('<input type="radio" value="" checked disabled>'),
470             array('<div data-value=""></div>'),
471             array('<select disabled></select>'),
472             array('<div ng-app></div>'),
473             array('<script defer></script>'),
474         );
475     }
476     /**
477      * @dataProvider booleanAttributes
478      */
479     public function testBooleanAttrs($html)
480     {
481         $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>'.$html.'</body></html>');
482
483         $stream = fopen('php://temp', 'w');
484         $r = new OutputRules($stream, $this->html5->getOptions());
485         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
486
487         $node = $dom->getElementsByTagName('body')->item(0)->firstChild;
488
489         $m = $this->getProtectedMethod('attrs');
490         $m->invoke($r, $node);
491
492         $content = stream_get_contents($stream, - 1, 0);
493
494         $html = preg_replace('~<[a-z]+(.*)></[a-z]+>~', '\1', $html);
495         $html = preg_replace('~<[a-z]+(.*)/?>~', '\1', $html);
496
497         $this->assertEquals($content, $html);
498
499     }
500
501     public function testAttrs()
502     {
503         $dom = $this->html5->loadHTML('<!doctype html>
504     <html lang="en">
505       <body>
506         <div id="foo" class="bar baz">foo bar baz</div>
507       </body>
508     </html>');
509
510         $stream = fopen('php://temp', 'w');
511         $r = new OutputRules($stream, $this->html5->getOptions());
512         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
513
514         $list = $dom->getElementsByTagName('div');
515
516         $m = $this->getProtectedMethod('attrs');
517         $m->invoke($r, $list->item(0));
518
519         $content = stream_get_contents($stream, - 1, 0);
520         $this->assertEquals(' id="foo" class="bar baz"', $content);
521     }
522
523     public function testSvg()
524     {
525         $dom = $this->html5->loadHTML(
526             '<!doctype html>
527     <html lang="en">
528       <body>
529         <div id="foo" class="bar baz">foo bar baz</div>
530         <svg width="150" height="100" viewBox="0 0 3 2">
531           <rect width="1" height="2" x="0" fill="#008d46" />
532           <rect width="1" height="2" x="1" fill="#ffffff" />
533           <rect width="1" height="2" x="2" fill="#d2232c" />
534           <rect id="Bar" x="300" y="100" width="300" height="100" fill="rgb(255,255,0)">
535             <animate attributeName="x" attributeType="XML" begin="0s" dur="9s" fill="freeze" from="300" to="0" />
536           </rect>
537         </svg>
538       </body>
539     </html>');
540
541         $stream = fopen('php://temp', 'w');
542         $r = new OutputRules($stream, $this->html5->getOptions());
543         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
544
545         $list = $dom->getElementsByTagName('svg');
546         $r->element($list->item(0));
547         $contents = stream_get_contents($stream, - 1, 0);
548         $this->assertRegExp('|<svg width="150" height="100" viewBox="0 0 3 2">|', $contents);
549         $this->assertRegExp('|<rect width="1" height="2" x="0" fill="#008d46" />|', $contents);
550         $this->assertRegExp('|<rect id="Bar" x="300" y="100" width="300" height="100" fill="rgb\(255,255,0\)">|', $contents);
551     }
552
553     public function testMath()
554     {
555         $dom = $this->html5->loadHTML(
556             '<!doctype html>
557     <html lang="en">
558       <body>
559         <div id="foo" class="bar baz">foo bar baz</div>
560         <math>
561           <mi>x</mi>
562           <csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">
563             <mo>&PlusMinus;</mo>
564           </csymbol>
565           <mi>y</mi>
566         </math>
567       </body>
568     </html>');
569
570         $stream = fopen('php://temp', 'w');
571         $r = new OutputRules($stream, $this->html5->getOptions());
572         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
573
574         $list = $dom->getElementsByTagName('math');
575         $r->element($list->item(0));
576         $content = stream_get_contents($stream, - 1, 0);
577         $this->assertRegExp('|<math>|', $content);
578         $this->assertRegExp('|<csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">|', $content);
579     }
580
581     public function testProcessorInstruction()
582     {
583         $dom = $this->html5->loadHTMLFragment('<?foo bar ?>');
584
585         $stream = fopen('php://temp', 'w');
586         $r = new OutputRules($stream, $this->html5->getOptions());
587         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
588
589         $r->processorInstruction($dom->firstChild);
590         $content = stream_get_contents($stream, - 1, 0);
591         $this->assertRegExp('|<\?foo bar \?>|', $content);
592     }
593
594     public function testAddressTag()
595     {
596         $dom = $this->html5->loadHTML(
597             '<!doctype html>
598     <html lang="en">
599       <body>
600         <address>
601             <a href="../People/Raggett/">Dave Raggett</a>,
602             <a href="../People/Arnaud/">Arnaud Le Hors</a>,
603             contact persons for the <a href="Activity">W3C HTML Activity</a>
604         </address>
605       </body>
606     </html>');
607
608         $stream = fopen('php://temp', 'w');
609         $r = new OutputRules($stream, $this->html5->getOptions());
610         $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
611
612         $list = $dom->getElementsByTagName('address');
613         $r->element($list->item(0));
614         $contents = stream_get_contents($stream, - 1, 0);
615
616         $this->assertRegExp('|<address>|', $contents);
617         $this->assertRegExp('|<a href="../People/Raggett/">Dave Raggett</a>,|', $contents);
618         $this->assertRegExp('|<a href="../People/Arnaud/">Arnaud Le Hors</a>,|', $contents);
619         $this->assertRegExp('|contact persons for the <a href="Activity">W3C HTML Activity</a>|', $contents);
620         $this->assertRegExp('|</address>|', $contents);
621     }
622
623     /**
624      * Ensure direct DOM manipulation doesn't break TEXT_RAW elements (iframe, script, etc...)
625      */
626     public function testHandlingInvalidRawContent()
627     {
628         $dom = $this->html5->loadHTML(
629     '<!doctype html>
630 <html lang="en" id="base">
631     <body>
632        <script id="template" type="x-tmpl-mustache">
633            <h1>Hello!</h1>
634        </script>
635     </body>
636 </html>');
637
638         $badNode = $dom->createElement("p", "Bar");
639
640         // modify the content of the TEXT_RAW element: <script id="template"> appending dom nodes
641         $styleElement = $dom->getElementById("template");
642         $styleElement->appendChild($badNode);
643
644         $contents = $this->html5->saveHTML($dom);
645
646         $this->assertTrue(strpos($contents, '<script id="template" type="x-tmpl-mustache">
647            <h1>Hello!</h1>
648        <p>Bar</p></script>')!==false);
649     }
650 }