Security update for Core, with self-updated composer
[yaffs-website] / vendor / masterminds / html5 / test / HTML5 / Serializer / OutputRulesTest.php
diff --git a/vendor/masterminds/html5/test/HTML5/Serializer/OutputRulesTest.php b/vendor/masterminds/html5/test/HTML5/Serializer/OutputRulesTest.php
new file mode 100644 (file)
index 0000000..72253d3
--- /dev/null
@@ -0,0 +1,650 @@
+<?php
+namespace Masterminds\HTML5\Tests\Serializer;
+
+use Masterminds\HTML5\Serializer\OutputRules;
+use Masterminds\HTML5\Serializer\Traverser;
+use Masterminds\HTML5;
+
+class OutputRulesTest extends \Masterminds\HTML5\Tests\TestCase
+{
+
+    protected $markup = '<!doctype html>
+    <html lang="en">
+      <head>
+        <meta charset="utf-8">
+        <title>Test</title>
+      </head>
+      <body>
+        <p>This is a test.</p>
+      </body>
+    </html>';
+
+    /**
+     * @var HTML5
+     */
+    protected $html5;
+
+    public function setUp()
+    {
+        $this->html5 = $this->getInstance();
+    }
+
+    /**
+     * Using reflection we make a protected method accessible for testing.
+     *
+     * @param string $name
+     *            The name of the method on the Traverser class to test.
+     *
+     * @return \ReflectionMethod for the specified method
+     */
+    public function getProtectedMethod($name)
+    {
+        $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\OutputRules');
+        $method = $class->getMethod($name);
+        $method->setAccessible(true);
+
+        return $method;
+    }
+
+    public function getTraverserProtectedProperty($name)
+    {
+        $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\Traverser');
+        $property = $class->getProperty($name);
+        $property->setAccessible(true);
+
+        return $property;
+    }
+
+    public function getOutputRules($options = array())
+    {
+        $options = $options + $this->html5->getOptions();
+        $stream = fopen('php://temp', 'w');
+        $dom = $this->html5->loadHTML($this->markup);
+        $r = new OutputRules($stream, $options);
+        $t = new Traverser($dom, $stream, $r, $options);
+
+        return array(
+            $r,
+            $stream
+        );
+    }
+
+    public function testDocument()
+    {
+        $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>foo</body></html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $r->document($dom);
+        $expected = '<!DOCTYPE html>' . PHP_EOL . '<html lang="en"><body>foo</body></html>' . PHP_EOL;
+        $this->assertEquals($expected, stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testEmptyDocument()
+    {
+       $dom = $this->html5->loadHTML('');
+
+       $stream = fopen('php://temp', 'w');
+       $r = new OutputRules($stream, $this->html5->getOptions());
+       $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+       $r->document($dom);
+       $expected = '<!DOCTYPE html>' . PHP_EOL;
+       $this->assertEquals($expected, stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testDoctype()
+    {
+        $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>foo</body></html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $m = $this->getProtectedMethod('doctype');
+        $m->invoke($r, 'foo');
+        $this->assertEquals("<!DOCTYPE html>" . PHP_EOL, stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testElement()
+    {
+        $dom = $this->html5->loadHTML(
+            '<!doctype html>
+    <html lang="en">
+      <body>
+        <div id="foo" class="bar baz">foo bar baz</div>
+        <svg width="150" height="100" viewBox="0 0 3 2">
+          <rect width="1" height="2" x="0" fill="#008d46" />
+          <rect width="1" height="2" x="1" fill="#ffffff" />
+          <rect width="1" height="2" x="2" fill="#d2232c" />
+        </svg>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('div');
+        $r->element($list->item(0));
+        $this->assertEquals('<div id="foo" class="bar baz">foo bar baz</div>', stream_get_contents($stream, - 1, 0));
+    }
+
+    function testSerializeWithNamespaces()
+    {
+        $this->html5 = $this->getInstance(array(
+            'xmlNamespaces' => true
+        ));
+
+        $source = '
+            <!DOCTYPE html>
+            <html><body id="body" xmlns:x="http://www.prefixed.com">
+                    <a id="bar1" xmlns="http://www.prefixed.com/bar1">
+                        <b id="bar4" xmlns="http://www.prefixed.com/bar4"><x:prefixed id="prefixed">xy</x:prefixed></b>
+                    </a>
+                    <svg id="svg">svg</svg>
+                    <c id="bar2" xmlns="http://www.prefixed.com/bar2"></c>
+                    <div id="div"></div>
+                    <d id="bar3"></d>
+                    <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>
+                </body>
+            </html>';
+
+        $dom = $this->html5->loadHTML($source, array(
+            'xmlNamespaces' => true
+        ));
+        $this->assertFalse($this->html5->hasErrors(), print_r($this->html5->getErrors(), 1));
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $t->walk();
+        $rendered = stream_get_contents($stream, - 1, 0);
+
+        $clear = function($s){
+            return trim(preg_replace('/[\s]+/', " ", $s));
+        };
+
+        $this->assertEquals($clear($source), $clear($rendered));
+    }
+
+    public function testElementWithScript()
+    {
+        $dom = $this->html5->loadHTML(
+            '<!doctype html>
+    <html lang="en">
+      <head>
+        <script>
+          var $jQ = jQuery.noConflict();
+          // Use jQuery via $jQ(...)
+          $jQ(document).ready(function () {
+            $jQ("#mktFrmSubmit").wrap("<div class=\'buttonSubmit\'></div>");
+            $jQ(".buttonSubmit").prepend("<span></span>");
+          });
+        </script>
+      </head>
+      <body>
+        <div id="foo" class="bar baz">foo bar baz</div>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $script = $dom->getElementsByTagName('script');
+        $r->element($script->item(0));
+        $this->assertEquals(
+            '<script>
+          var $jQ = jQuery.noConflict();
+          // Use jQuery via $jQ(...)
+          $jQ(document).ready(function () {
+            $jQ("#mktFrmSubmit").wrap("<div class=\'buttonSubmit\'></div>");
+            $jQ(".buttonSubmit").prepend("<span></span>");
+          });
+        </script>', stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testElementWithStyle()
+    {
+        $dom = $this->html5->loadHTML(
+            '<!doctype html>
+    <html lang="en">
+      <head>
+        <style>
+          body > .bar {
+            display: none;
+          }
+        </style>
+      </head>
+      <body>
+        <div id="foo" class="bar baz">foo bar baz</div>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $style = $dom->getElementsByTagName('style');
+        $r->element($style->item(0));
+        $this->assertEquals('<style>
+          body > .bar {
+            display: none;
+          }
+        </style>', stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testOpenTag()
+    {
+        $dom = $this->html5->loadHTML('<!doctype html>
+    <html lang="en">
+      <body>
+        <div id="foo" class="bar baz">foo bar baz</div>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('div');
+        $m = $this->getProtectedMethod('openTag');
+        $m->invoke($r, $list->item(0));
+        $this->assertEquals('<div id="foo" class="bar baz">', stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testCData()
+    {
+        $dom = $this->html5->loadHTML('<!doctype html>
+    <html lang="en">
+      <body>
+        <div><![CDATA[bar]]></div>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('div');
+        $r->cdata($list->item(0)->childNodes->item(0));
+        $this->assertEquals('<![CDATA[bar]]>', stream_get_contents($stream, - 1, 0));
+
+        $dom = $this->html5->loadHTML('<!doctype html>
+    <html lang="en">
+      <body>
+        <div id="foo"></div>
+      </body>
+    </html>');
+
+        $dom->getElementById('foo')->appendChild(new \DOMCdataSection("]]>Foo<[![CDATA test ]]>"));
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+        $list = $dom->getElementsByTagName('div');
+        $r->cdata($list->item(0)->childNodes->item(0));
+
+        $this->assertEquals('<![CDATA[]]]]><![CDATA[>Foo<[![CDATA test ]]]]><![CDATA[>]]>', stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testComment()
+    {
+        $dom = $this->html5->loadHTML('<!doctype html>
+    <html lang="en">
+      <body>
+        <div><!-- foo --></div>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('div');
+        $r->comment($list->item(0)->childNodes->item(0));
+        $this->assertEquals('<!-- foo -->', stream_get_contents($stream, - 1, 0));
+
+        $dom = $this->html5->loadHTML('<!doctype html>
+    <html lang="en">
+      <body>
+        <div id="foo"></div>
+      </body>
+      </html>');
+        $dom->getElementById('foo')->appendChild(new \DOMComment('<!-- --> --> Foo -->'));
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('div');
+        $r->comment($list->item(0)->childNodes->item(0));
+
+        // Could not find more definitive guidelines on what this should be. Went with
+        // what the HTML5 spec says and what \DOMDocument::saveXML() produces.
+        $this->assertEquals('<!--<!-- --> --> Foo -->-->', stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testText()
+    {
+        $dom = $this->html5->loadHTML('<!doctype html>
+    <html lang="en">
+      <head>
+        <script>baz();</script>
+      </head>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('script');
+        $r->text($list->item(0)->childNodes->item(0));
+        $this->assertEquals('baz();', stream_get_contents($stream, - 1, 0));
+
+        $dom = $this->html5->loadHTML('<!doctype html>
+    <html lang="en">
+      <head id="foo"></head>
+    </html>');
+        $foo = $dom->getElementById('foo');
+        $foo->appendChild(new \DOMText('<script>alert("hi");</script>'));
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $r->text($foo->firstChild);
+        $this->assertEquals('&lt;script&gt;alert("hi");&lt;/script&gt;', stream_get_contents($stream, - 1, 0));
+    }
+
+    public function testNl()
+    {
+        list ($o, $s) = $this->getOutputRules();
+
+        $m = $this->getProtectedMethod('nl');
+        $m->invoke($o);
+        $this->assertEquals(PHP_EOL, stream_get_contents($s, - 1, 0));
+    }
+
+    public function testWr()
+    {
+        list ($o, $s) = $this->getOutputRules();
+
+        $m = $this->getProtectedMethod('wr');
+        $m->invoke($o, 'foo');
+        $this->assertEquals('foo', stream_get_contents($s, - 1, 0));
+    }
+
+    public function getEncData()
+    {
+        return array(
+            array(
+                false,
+                '&\'<>"',
+                '&amp;\'&lt;&gt;"',
+                '&amp;&apos;&lt;&gt;&quot;'
+            ),
+            array(
+                false,
+                'This + is. a < test',
+                'This + is. a &lt; test',
+                'This &plus; is&period; a &lt; test'
+            ),
+            array(
+                false,
+                '.+#',
+                '.+#',
+                '&period;&plus;&num;'
+            ),
+
+            array(
+                true,
+                '.+#\'',
+                '.+#\'',
+                '&period;&plus;&num;&apos;'
+            ),
+            array(
+                true,
+                '&".<',
+                '&amp;&quot;.<',
+                '&amp;&quot;&period;&lt;'
+            ),
+            array(
+                true,
+                '&\'<>"',
+                '&amp;\'<>&quot;',
+                '&amp;&apos;&lt;&gt;&quot;'
+            ),
+            array(
+                true,
+                "\xc2\xa0\"'",
+                '&nbsp;&quot;\'',
+                '&nbsp;&quot;&apos;'
+            )
+        );
+    }
+
+    /**
+     * Test basic encoding of text.
+     * @dataProvider getEncData
+     */
+    public function testEnc($isAttribute, $test, $expected, $expectedEncoded)
+    {
+        list ($o, $s) = $this->getOutputRules();
+        $m = $this->getProtectedMethod('enc');
+
+        $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute));
+
+        list ($o, $s) = $this->getOutputRules(array(
+            'encode_entities' => true
+        ));
+        $m = $this->getProtectedMethod('enc');
+        $this->assertEquals($expectedEncoded, $m->invoke($o, $test, $isAttribute));
+    }
+
+    /**
+     * Test basic encoding of text.
+     * @dataProvider getEncData
+     */
+    public function testEscape($isAttribute, $test, $expected, $expectedEncoded)
+    {
+        list ($o, $s) = $this->getOutputRules();
+        $m = $this->getProtectedMethod('escape');
+
+        $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute));
+    }
+
+    public function booleanAttributes()
+    {
+        return array(
+            array('<img alt="" ismap>'),
+            array('<img alt="">'),
+            array('<input type="radio" readonly>'),
+            array('<input type="radio" checked disabled>'),
+            array('<input type="checkbox" checked disabled>'),
+            array('<input type="radio" value="" checked disabled>'),
+            array('<div data-value=""></div>'),
+            array('<select disabled></select>'),
+            array('<div ng-app></div>'),
+            array('<script defer></script>'),
+        );
+    }
+    /**
+     * @dataProvider booleanAttributes
+     */
+    public function testBooleanAttrs($html)
+    {
+        $dom = $this->html5->loadHTML('<!doctype html><html lang="en"><body>'.$html.'</body></html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $node = $dom->getElementsByTagName('body')->item(0)->firstChild;
+
+        $m = $this->getProtectedMethod('attrs');
+        $m->invoke($r, $node);
+
+        $content = stream_get_contents($stream, - 1, 0);
+
+        $html = preg_replace('~<[a-z]+(.*)></[a-z]+>~', '\1', $html);
+        $html = preg_replace('~<[a-z]+(.*)/?>~', '\1', $html);
+
+        $this->assertEquals($content, $html);
+
+    }
+
+    public function testAttrs()
+    {
+        $dom = $this->html5->loadHTML('<!doctype html>
+    <html lang="en">
+      <body>
+        <div id="foo" class="bar baz">foo bar baz</div>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('div');
+
+        $m = $this->getProtectedMethod('attrs');
+        $m->invoke($r, $list->item(0));
+
+        $content = stream_get_contents($stream, - 1, 0);
+        $this->assertEquals(' id="foo" class="bar baz"', $content);
+    }
+
+    public function testSvg()
+    {
+        $dom = $this->html5->loadHTML(
+            '<!doctype html>
+    <html lang="en">
+      <body>
+        <div id="foo" class="bar baz">foo bar baz</div>
+        <svg width="150" height="100" viewBox="0 0 3 2">
+          <rect width="1" height="2" x="0" fill="#008d46" />
+          <rect width="1" height="2" x="1" fill="#ffffff" />
+          <rect width="1" height="2" x="2" fill="#d2232c" />
+          <rect id="Bar" x="300" y="100" width="300" height="100" fill="rgb(255,255,0)">
+            <animate attributeName="x" attributeType="XML" begin="0s" dur="9s" fill="freeze" from="300" to="0" />
+          </rect>
+        </svg>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('svg');
+        $r->element($list->item(0));
+        $contents = stream_get_contents($stream, - 1, 0);
+        $this->assertRegExp('|<svg width="150" height="100" viewBox="0 0 3 2">|', $contents);
+        $this->assertRegExp('|<rect width="1" height="2" x="0" fill="#008d46" />|', $contents);
+        $this->assertRegExp('|<rect id="Bar" x="300" y="100" width="300" height="100" fill="rgb\(255,255,0\)">|', $contents);
+    }
+
+    public function testMath()
+    {
+        $dom = $this->html5->loadHTML(
+            '<!doctype html>
+    <html lang="en">
+      <body>
+        <div id="foo" class="bar baz">foo bar baz</div>
+        <math>
+          <mi>x</mi>
+          <csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">
+            <mo>&PlusMinus;</mo>
+          </csymbol>
+          <mi>y</mi>
+        </math>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('math');
+        $r->element($list->item(0));
+        $content = stream_get_contents($stream, - 1, 0);
+        $this->assertRegExp('|<math>|', $content);
+        $this->assertRegExp('|<csymbol definitionURL="http://www.example.com/mathops/multiops.html#plusminus">|', $content);
+    }
+
+    public function testProcessorInstruction()
+    {
+        $dom = $this->html5->loadHTMLFragment('<?foo bar ?>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $r->processorInstruction($dom->firstChild);
+        $content = stream_get_contents($stream, - 1, 0);
+        $this->assertRegExp('|<\?foo bar \?>|', $content);
+    }
+
+    public function testAddressTag()
+    {
+        $dom = $this->html5->loadHTML(
+            '<!doctype html>
+    <html lang="en">
+      <body>
+        <address>
+            <a href="../People/Raggett/">Dave Raggett</a>,
+            <a href="../People/Arnaud/">Arnaud Le Hors</a>,
+            contact persons for the <a href="Activity">W3C HTML Activity</a>
+        </address>
+      </body>
+    </html>');
+
+        $stream = fopen('php://temp', 'w');
+        $r = new OutputRules($stream, $this->html5->getOptions());
+        $t = new Traverser($dom, $stream, $r, $this->html5->getOptions());
+
+        $list = $dom->getElementsByTagName('address');
+        $r->element($list->item(0));
+        $contents = stream_get_contents($stream, - 1, 0);
+
+        $this->assertRegExp('|<address>|', $contents);
+        $this->assertRegExp('|<a href="../People/Raggett/">Dave Raggett</a>,|', $contents);
+        $this->assertRegExp('|<a href="../People/Arnaud/">Arnaud Le Hors</a>,|', $contents);
+        $this->assertRegExp('|contact persons for the <a href="Activity">W3C HTML Activity</a>|', $contents);
+        $this->assertRegExp('|</address>|', $contents);
+    }
+
+    /**
+     * Ensure direct DOM manipulation doesn't break TEXT_RAW elements (iframe, script, etc...)
+     */
+    public function testHandlingInvalidRawContent()
+    {
+        $dom = $this->html5->loadHTML(
+    '<!doctype html>
+<html lang="en" id="base">
+    <body>
+       <script id="template" type="x-tmpl-mustache">
+           <h1>Hello!</h1>
+       </script>
+    </body>
+</html>');
+
+        $badNode = $dom->createElement("p", "Bar");
+
+        // modify the content of the TEXT_RAW element: <script id="template"> appending dom nodes
+        $styleElement = $dom->getElementById("template");
+        $styleElement->appendChild($badNode);
+
+        $contents = $this->html5->saveHTML($dom);
+
+        $this->assertTrue(strpos($contents, '<script id="template" type="x-tmpl-mustache">
+           <h1>Hello!</h1>
+       <p>Bar</p></script>')!==false);
+    }
+}