diff options
Diffstat (limited to 'lib/htmlpurifier/tests')
284 files changed, 15565 insertions, 0 deletions
diff --git a/lib/htmlpurifier/tests/CliTestCase.php b/lib/htmlpurifier/tests/CliTestCase.php new file mode 100644 index 000000000..36b728e16 --- /dev/null +++ b/lib/htmlpurifier/tests/CliTestCase.php @@ -0,0 +1,81 @@ +<?php + +/** + * Implements an external test-case like RemoteTestCase that parses its + * output from XML returned by a command line call + */ +class CliTestCase +{ + public $_command; + public $_out = false; + public $_quiet = false; + public $_errors = array(); + public $_size = false; + /** + * @param $command Command to execute to retrieve XML + * @param $xml Whether or not to suppress error messages + */ + public function __construct($command, $quiet = false, $size = false) { + $this->_command = $command; + $this->_quiet = $quiet; + $this->_size = $size; + } + public function getLabel() { + return $this->_command; + } + public function run($reporter) { + if (!$this->_quiet) $reporter->paintFormattedMessage('Running ['.$this->_command.']'); + return $this->_invokeCommand($this->_command, $reporter); + } + public function _invokeCommand($command, $reporter) { + $xml = shell_exec($command); + if (! $xml) { + if (!$this->_quiet) { + $reporter->paintFail('Command did not have any output [' . $command . ']'); + } + return false; + } + $parser = $this->_createParser($reporter); + + set_error_handler(array($this, '_errorHandler')); + $status = $parser->parse($xml); + restore_error_handler(); + + if (! $status) { + if (!$this->_quiet) { + foreach ($this->_errors as $error) { + list($no, $str, $file, $line) = $error; + $reporter->paintFail("Error $no: $str on line $line of $file"); + } + if (strlen($xml) > 120) { + $msg = substr($xml, 0, 50) . "...\n\n[snip]\n\n..." . substr($xml, -50); + } else { + $msg = $xml; + } + $reporter->paintFail("Command produced malformed XML"); + $reporter->paintFormattedMessage($msg); + } + return false; + } + return true; + } + public function _createParser($reporter) { + $parser = new SimpleTestXmlParser($reporter); + return $parser; + } + public function getSize() { + // This code properly does the dry run and allows for proper test + // case reporting but it's REALLY slow, so I don't recommend it. + if ($this->_size === false) { + $reporter = new SimpleReporter(); + $this->_invokeCommand($this->_command . ' --dry', $reporter); + $this->_size = $reporter->getTestCaseCount(); + } + return $this->_size; + } + public function _errorHandler($a, $b, $c, $d) { + $this->_errors[] = array($a, $b, $c, $d); // see set_error_handler() + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/Debugger.php b/lib/htmlpurifier/tests/Debugger.php new file mode 100644 index 000000000..45e107f44 --- /dev/null +++ b/lib/htmlpurifier/tests/Debugger.php @@ -0,0 +1,147 @@ +<?php + +/** + * Debugging tools. + * + * This file gives a developer a set of tools useful for performing code + * consistency checks. This includes scoping code blocks to ensure that + * only the interesting iteration of a loop gets outputted, a paint() + * function that acts like var_dump() with pre tags, and conditional + * printing. + */ + +/* +TODO + * Integrate into SimpleTest so it tells us whether or not there were any + not cleaned up debug calls. + * Custom var_dump() that ignores blacklisted properties + * DEPRECATE AND REMOVE ALL CALLS! +*/ + +/**#@+ + * Convenience global functions. Corresponds to method on Debugger. + */ +function paint($mixed) { + $Debugger =& Debugger::instance(); + return $Debugger->paint($mixed); +} +function paintIf($mixed, $conditional) { + $Debugger =& Debugger::instance(); + return $Debugger->paintIf($mixed, $conditional); +} +function paintWhen($mixed, $scopes = array()) { + $Debugger =& Debugger::instance(); + return $Debugger->paintWhen($mixed, $scopes); +} +function paintIfWhen($mixed, $conditional, $scopes = array()) { + $Debugger =& Debugger::instance(); + return $Debugger->paintIfWhen($mixed, $conditional, $scopes); +} +function addScope($id = false) { + $Debugger =& Debugger::instance(); + return $Debugger->addScope($id); +} +function removeScope($id) { + $Debugger =& Debugger::instance(); + return $Debugger->removeScope($id); +} +function resetScopes() { + $Debugger =& Debugger::instance(); + return $Debugger->resetScopes(); +} +function isInScopes($array = array()) { + $Debugger =& Debugger::instance(); + return $Debugger->isInScopes($array); +} +/**#@-*/ + + +/** + * The debugging singleton. Most interesting stuff happens here. + */ +class Debugger +{ + + public $shouldPaint = false; + public $paints = 0; + public $current_scopes = array(); + public $scope_nextID = 1; + public $add_pre = true; + + public function Debugger() { + $this->add_pre = !extension_loaded('xdebug'); + } + + public static function &instance() { + static $soleInstance = false; + if (!$soleInstance) $soleInstance = new Debugger(); + return $soleInstance; + } + + public function paintIf($mixed, $conditional) { + if (!$conditional) return; + $this->paint($mixed); + } + + public function paintWhen($mixed, $scopes = array()) { + if (!$this->isInScopes($scopes)) return; + $this->paint($mixed); + } + + public function paintIfWhen($mixed, $conditional, $scopes = array()) { + if (!$conditional) return; + if (!$this->isInScopes($scopes)) return; + $this->paint($mixed); + } + + public function paint($mixed) { + $this->paints++; + if($this->add_pre) echo '<pre>'; + var_dump($mixed); + if($this->add_pre) echo '</pre>'; + } + + public function addScope($id = false) { + if ($id == false) { + $id = $this->scope_nextID++; + } + $this->current_scopes[$id] = true; + } + + public function removeScope($id) { + if (isset($this->current_scopes[$id])) unset($this->current_scopes[$id]); + } + + public function resetScopes() { + $this->current_scopes = array(); + $this->scope_nextID = 1; + } + + public function isInScopes($scopes = array()) { + if (empty($this->current_scopes)) { + return false; + } + if (!is_array($scopes)) { + $scopes = array($scopes); + } + foreach ($scopes as $scope_id) { + if (empty($this->current_scopes[$scope_id])) { + return false; + } + } + if (empty($scopes)) { + if ($this->scope_nextID == 1) { + return false; + } + for($i = 1; $i < $this->scope_nextID; $i++) { + if (empty($this->current_scopes[$i])) { + return false; + } + } + } + return true; + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/FSTools/FileSystemHarness.php b/lib/htmlpurifier/tests/FSTools/FileSystemHarness.php new file mode 100644 index 000000000..710196e4f --- /dev/null +++ b/lib/htmlpurifier/tests/FSTools/FileSystemHarness.php @@ -0,0 +1,36 @@ +<?php + +/** + * Test harness that sets up a filesystem sandbox for file-emulation + * functions to safely unit test in. + * + * @todo Make an automatic FSTools mock or something + */ +class FSTools_FileSystemHarness extends UnitTestCase +{ + + protected $dir, $oldDir; + + function __construct() { + parent::__construct(); + $this->dir = 'tmp/' . md5(uniqid(rand(), true)) . '/'; + mkdir($this->dir); + $this->oldDir = getcwd(); + + } + + function __destruct() { + FSTools::singleton()->rmdirr($this->dir); + } + + function setup() { + chdir($this->dir); + } + + function tearDown() { + chdir($this->oldDir); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/FSTools/FileTest.php b/lib/htmlpurifier/tests/FSTools/FileTest.php new file mode 100644 index 000000000..e9b703a2d --- /dev/null +++ b/lib/htmlpurifier/tests/FSTools/FileTest.php @@ -0,0 +1,46 @@ +<?php + +require_once 'FSTools/FileSystemHarness.php'; + +/** + * These are not really unit tests, just sanity checks of basic functionality. + */ +class FSTools_FileTest extends FSTools_FileSystemHarness +{ + + function test() { + $name = 'test.txt'; + $file = new FSTools_File($name); + $this->assertFalse($file->exists()); + $file->write('foobar'); + $this->assertTrue($file->exists()); + $this->assertEqual($file->get(), 'foobar'); + $file->delete(); + $this->assertFalse($file->exists()); + } + + function testGetNonExistent() { + $name = 'notfound.txt'; + $file = new FSTools_File($name); + $this->expectError(); + $this->assertFalse($file->get()); + } + + function testHandle() { + $file = new FSTools_File('foo.txt'); + $this->assertFalse($file->exists()); + $file->open('w'); + $this->assertTrue($file->exists()); + $file->put('Foobar'); + $file->close(); + $file->open('r'); + $this->assertIdentical('F', $file->getChar()); + $this->assertFalse($file->eof()); + $this->assertIdentical('oo', $file->read(2)); + $this->assertIdentical('bar', $file->getLine()); + $this->assertTrue($file->eof()); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php new file mode 100644 index 000000000..227bc9535 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php @@ -0,0 +1,134 @@ +<?php + +Mock::generatePartial( + 'HTMLPurifier_AttrCollections', + 'HTMLPurifier_AttrCollections_TestForConstruct', + array('performInclusions', 'expandIdentifiers') +); + +class HTMLPurifier_AttrCollectionsTest extends HTMLPurifier_Harness +{ + + function testConstruction() { + + generate_mock_once('HTMLPurifier_AttrTypes'); + + $collections = new HTMLPurifier_AttrCollections_TestForConstruct(); + + $types = new HTMLPurifier_AttrTypesMock(); + + $modules = array(); + + $modules['Module1'] = new HTMLPurifier_HTMLModule(); + $modules['Module1']->attr_collections = array( + 'Core' => array( + 0 => array('Soup', 'Undefined'), + 'attribute' => 'Type', + 'attribute-2' => 'Type2', + ), + 'Soup' => array( + 'attribute-3' => 'Type3-old' // overwritten + ) + ); + + $modules['Module2'] = new HTMLPurifier_HTMLModule(); + $modules['Module2']->attr_collections = array( + 'Core' => array( + 0 => array('Brocolli') + ), + 'Soup' => array( + 'attribute-3' => 'Type3' + ), + 'Brocolli' => array() + ); + + $collections->__construct($types, $modules); + // this is without identifier expansion or inclusions + $this->assertIdentical( + $collections->info, + array( + 'Core' => array( + 0 => array('Soup', 'Undefined', 'Brocolli'), + 'attribute' => 'Type', + 'attribute-2' => 'Type2' + ), + 'Soup' => array( + 'attribute-3' => 'Type3' + ), + 'Brocolli' => array() + ) + ); + + } + + function test_performInclusions() { + + generate_mock_once('HTMLPurifier_AttrTypes'); + + $types = new HTMLPurifier_AttrTypesMock(); + $collections = new HTMLPurifier_AttrCollections($types, array()); + $collections->info = array( + 'Core' => array(0 => array('Inclusion', 'Undefined'), 'attr-original' => 'Type'), + 'Inclusion' => array(0 => array('SubInclusion'), 'attr' => 'Type'), + 'SubInclusion' => array('attr2' => 'Type') + ); + + $collections->performInclusions($collections->info['Core']); + $this->assertIdentical( + $collections->info['Core'], + array( + 'attr-original' => 'Type', + 'attr' => 'Type', + 'attr2' => 'Type' + ) + ); + + // test recursive + $collections->info = array( + 'One' => array(0 => array('Two'), 'one' => 'Type'), + 'Two' => array(0 => array('One'), 'two' => 'Type') + ); + $collections->performInclusions($collections->info['One']); + $this->assertIdentical( + $collections->info['One'], + array( + 'one' => 'Type', + 'two' => 'Type' + ) + ); + + } + + function test_expandIdentifiers() { + + generate_mock_once('HTMLPurifier_AttrTypes'); + + $types = new HTMLPurifier_AttrTypesMock(); + $collections = new HTMLPurifier_AttrCollections($types, array()); + + $attr = array( + 'attr1' => 'Color', + 'attr2*' => 'URI' + ); + $c_object = new HTMLPurifier_AttrDef_HTML_Color(); + $u_object = new HTMLPurifier_AttrDef_URI(); + + $types->setReturnValue('get', $c_object, array('Color')); + $types->setReturnValue('get', $u_object, array('URI')); + + $collections->expandIdentifiers($attr, $types); + + $u_object->required = true; + $this->assertIdentical( + $attr, + array( + 'attr1' => $c_object, + 'attr2' => $u_object + ) + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php new file mode 100644 index 000000000..56efa306f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php @@ -0,0 +1,28 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_AlphaValueTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + + $this->assertDef('0'); + $this->assertDef('1'); + $this->assertDef('.2'); + + // clamping to [0.0, 1,0] + $this->assertDef('1.2', '1'); + $this->assertDef('-3', '0'); + + $this->assertDef('0.0', '0'); + $this->assertDef('1.0', '1'); + $this->assertDef('000', '0'); + + $this->assertDef('asdf', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php new file mode 100644 index 000000000..a216b2677 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php @@ -0,0 +1,68 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_BackgroundPositionTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); + + // explicitly cited in spec + $this->assertDef('0% 0%'); + $this->assertDef('100% 100%'); + $this->assertDef('14% 84%'); + $this->assertDef('2cm 1cm'); + $this->assertDef('top'); + $this->assertDef('left'); + $this->assertDef('center'); + $this->assertDef('right'); + $this->assertDef('bottom'); + $this->assertDef('left top'); + $this->assertDef('center top'); + $this->assertDef('right top'); + $this->assertDef('left center'); + $this->assertDef('right center'); + $this->assertDef('left bottom'); + $this->assertDef('center bottom'); + $this->assertDef('right bottom'); + + // reordered due to internal impl details + $this->assertDef('top left', 'left top'); + $this->assertDef('top center', 'top'); + $this->assertDef('top right', 'right top'); + $this->assertDef('center left', 'left'); + $this->assertDef('center center', 'center'); + $this->assertDef('center right', 'right'); + $this->assertDef('bottom left', 'left bottom'); + $this->assertDef('bottom center', 'bottom'); + $this->assertDef('bottom right', 'right bottom'); + + // more cases from the defined syntax + $this->assertDef('1.32in 4ex'); + $this->assertDef('-14% -84.65%'); + $this->assertDef('-1in -4ex'); + $this->assertDef('-1pc 2.3%'); + + // keyword mixing + $this->assertDef('3em top'); + $this->assertDef('left 50%'); + + // fixable keyword mixing + $this->assertDef('top 3em', '3em top'); + $this->assertDef('50% left', 'left 50%'); + + // whitespace collapsing + $this->assertDef('3em top', '3em top'); + $this->assertDef("left\n \t foo ", 'left'); + + // invalid uses (we're going to be strict on these) + $this->assertDef('foo bar', false); + $this->assertDef('left left', 'left'); + $this->assertDef('left right top bottom center left', 'left bottom'); + $this->assertDef('0fr 9%', '9%'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php new file mode 100644 index 000000000..83461c365 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php @@ -0,0 +1,23 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_BackgroundTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $config = HTMLPurifier_Config::createDefault(); + $this->def = new HTMLPurifier_AttrDef_CSS_Background($config); + + $valid = '#333 url("chess.png") repeat fixed 50% top'; + $this->assertDef($valid); + $this->assertDef('url(\'chess.png\') #333 50% top repeat fixed', $valid); + $this->assertDef( + 'rgb(34, 56, 33) url(chess.png) repeat fixed top', + 'rgb(34,56,33) url("chess.png") repeat fixed top' + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php new file mode 100644 index 000000000..6cd77fd7a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php @@ -0,0 +1,21 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_BorderTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $config = HTMLPurifier_Config::createDefault(); + $this->def = new HTMLPurifier_AttrDef_CSS_Border($config); + + $this->assertDef('thick solid red', 'thick solid #FF0000'); + $this->assertDef('thick solid'); + $this->assertDef('solid red', 'solid #FF0000'); + $this->assertDef('1px solid #000'); + $this->assertDef('1px solid rgb(0, 0, 0)', '1px solid rgb(0,0,0)'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php new file mode 100644 index 000000000..f3a74e897 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php @@ -0,0 +1,41 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_ColorTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_Color(); + + $this->assertDef('#F00'); + $this->assertDef('#fff'); + $this->assertDef('#eeeeee'); + $this->assertDef('#808080'); + $this->assertDef('rgb(255, 0, 0)', 'rgb(255,0,0)'); // rm spaces + $this->assertDef('rgb(100%,0%,0%)'); + $this->assertDef('rgb(50.5%,23.2%,43.9%)'); // decimals okay + + $this->assertDef('#G00', false); + $this->assertDef('cmyk(40, 23, 43, 23)', false); + $this->assertDef('rgb(0%, 23, 68%)', false); + + // clip numbers outside sRGB gamut + $this->assertDef('rgb(200%, -10%, 0%)', 'rgb(100%,0%,0%)'); + $this->assertDef('rgb(256,-23,34)', 'rgb(255,0,34)'); + + // color keywords, of course + $this->assertDef('red', '#FF0000'); + + // malformed hex declaration + $this->assertDef('808080', '#808080'); + $this->assertDef('000000', '#000000'); + $this->assertDef('fed', '#fed'); + + // maybe hex transformations would be another nice feature + // at the very least transform rgb percent to rgb integer + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php new file mode 100644 index 000000000..44bef5551 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php @@ -0,0 +1,81 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_Composite_Testable extends + HTMLPurifier_AttrDef_CSS_Composite +{ + + // we need to pass by ref to get the mocks in + function HTMLPurifier_AttrDef_CSS_Composite_Testable(&$defs) { + $this->defs =& $defs; + } + +} + +class HTMLPurifier_AttrDef_CSS_CompositeTest extends HTMLPurifier_AttrDefHarness +{ + + protected $def1, $def2; + + function test() { + + generate_mock_once('HTMLPurifier_AttrDef'); + + $config = HTMLPurifier_Config::createDefault(); + $context = new HTMLPurifier_Context(); + + // first test: value properly validates on first definition + // so second def is never called + + $def1 = new HTMLPurifier_AttrDefMock(); + $def2 = new HTMLPurifier_AttrDefMock(); + $defs = array(&$def1, &$def2); + $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs); + $input = 'FOOBAR'; + $output = 'foobar'; + $def1_params = array($input, $config, $context); + $def1->expectOnce('validate', $def1_params); + $def1->setReturnValue('validate', $output, $def1_params); + $def2->expectNever('validate'); + + $result = $def->validate($input, $config, $context); + $this->assertIdentical($output, $result); + + // second test, first def fails, second def works + + $def1 = new HTMLPurifier_AttrDefMock(); + $def2 = new HTMLPurifier_AttrDefMock(); + $defs = array(&$def1, &$def2); + $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs); + $input = 'BOOMA'; + $output = 'booma'; + $def_params = array($input, $config, $context); + $def1->expectOnce('validate', $def_params); + $def1->setReturnValue('validate', false, $def_params); + $def2->expectOnce('validate', $def_params); + $def2->setReturnValue('validate', $output, $def_params); + + $result = $def->validate($input, $config, $context); + $this->assertIdentical($output, $result); + + // third test, all fail, so composite faiils + + $def1 = new HTMLPurifier_AttrDefMock(); + $def2 = new HTMLPurifier_AttrDefMock(); + $defs = array(&$def1, &$def2); + $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs); + $input = 'BOOMA'; + $output = false; + $def_params = array($input, $config, $context); + $def1->expectOnce('validate', $def_params); + $def1->setReturnValue('validate', false, $def_params); + $def2->expectOnce('validate', $def_params); + $def2->setReturnValue('validate', false, $def_params); + + $result = $def->validate($input, $config, $context); + $this->assertIdentical($output, $result); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php new file mode 100644 index 000000000..7795643f1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php @@ -0,0 +1,29 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_FilterTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_Filter(); + + $this->assertDef('none'); + + $this->assertDef('alpha(opacity=0)'); + $this->assertDef('alpha(opacity=100)'); + $this->assertDef('alpha(opacity=50)'); + $this->assertDef('alpha(opacity=342)', 'alpha(opacity=100)'); + $this->assertDef('alpha(opacity=-23)', 'alpha(opacity=0)'); + + $this->assertDef('alpha ( opacity = 0 )', 'alpha(opacity=0)'); + $this->assertDef('alpha(opacity=0,opacity=100)', 'alpha(opacity=0)'); + + $this->assertDef('progid:DXImageTransform.Microsoft.Alpha(opacity=20)'); + + $this->assertDef('progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php new file mode 100644 index 000000000..fda8e01ff --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php @@ -0,0 +1,52 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_FontFamilyTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_FontFamily(); + + $this->assertDef('Gill, Helvetica, sans-serif'); + $this->assertDef("'Times New Roman', serif"); + $this->assertDef("\"Times New Roman\"", "'Times New Roman'"); + $this->assertDef('01234'); + $this->assertDef(',', false); + $this->assertDef('Times New Roman, serif', "'Times New Roman', serif"); + $this->assertDef($d = "'\xE5\xAE\x8B\xE4\xBD\x93'"); + $this->assertDef("\xE5\xAE\x8B\xE4\xBD\x93", $d); + $this->assertDef("'\\01'", "''"); + $this->assertDef("'\\20'", "' '"); + $this->assertDef("\\0020", "' '"); + $this->assertDef("'\\000045'", "E"); + $this->assertDef("','", false); + $this->assertDef("',' foobar','", "' foobar'"); + $this->assertDef("'\\000045a'", "Ea"); + $this->assertDef("'\\00045 a'", "Ea"); + $this->assertDef("'\\00045 a'", "'E a'"); + $this->assertDef("'\\\nf'", "f"); + // No longer supported, except maybe in NoJS mode (see source + // file for more explanation) + //$this->assertDef($d = '"John\'s Font"'); + //$this->assertDef("John's Font", $d); + //$this->assertDef("'\\','f'", "\"\\5C \", f"); + //$this->assertDef("'\\27'", "\"'\""); + //$this->assertDef('"\\22"', "\"\\22 \""); + //$this->assertDef('"\\""', "\"\\22 \""); + //$this->assertDef('"\'"', "\"'\""); + } + + function testAllowed() { + $this->config->set('CSS.AllowedFonts', array('serif', 'Times New Roman')); + + $this->assertDef('serif'); + $this->assertDef('sans-serif', false); + $this->assertDef('serif, sans-serif', 'serif'); + $this->assertDef('Times New Roman', "'Times New Roman'"); + $this->assertDef("'Times New Roman'"); + $this->assertDef('foo', false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php new file mode 100644 index 000000000..91870d13e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php @@ -0,0 +1,34 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_FontTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $config = HTMLPurifier_Config::createDefault(); + $this->def = new HTMLPurifier_AttrDef_CSS_Font($config); + + // hodgepodge of usage cases from W3C spec, but " -> ' + $this->assertDef('12px/14px sans-serif'); + $this->assertDef('80% sans-serif'); + $this->assertDef("x-large/110% 'New Century Schoolbook', serif"); + $this->assertDef('bold italic large Palatino, serif'); + $this->assertDef('normal small-caps 120%/120% fantasy'); + $this->assertDef("300 italic 1.3em/1.7em 'FB Armada', sans-serif"); + $this->assertDef('600 9px Charcoal'); + $this->assertDef('600 9px/ 12px Charcoal', '600 9px/12px Charcoal'); + + // spacing + $this->assertDef('12px / 14px sans-serif', '12px/14px sans-serif'); + + // system fonts + $this->assertDef('menu'); + + $this->assertDef('800', false); + $this->assertDef('600 9px//12px Charcoal', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php new file mode 100644 index 000000000..c7fa8a0fa --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php @@ -0,0 +1,49 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_ImportantDecoratorTest extends HTMLPurifier_AttrDefHarness +{ + + /** Mock AttrDef decorator is wrapping */ + protected $mock; + + function setUp() { + generate_mock_once('HTMLPurifier_AttrDef'); + $this->mock = new HTMLPurifier_AttrDefMock(); + $this->def = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($this->mock, true); + } + + protected function setMock($input, $output = null) { + if ($output === null) $output = $input; + $this->mock->expectOnce('validate', array($input, $this->config, $this->context)); + $this->mock->setReturnValue('validate', $output); + } + + function testImportant() { + $this->setMock('23'); + $this->assertDef('23 !important'); + } + + function testImportantInternalDefChanged() { + $this->setMock('23', '24'); + $this->assertDef('23 !important', '24 !important'); + } + + function testImportantWithSpace() { + $this->setMock('23'); + $this->assertDef('23 ! important ', '23 !important'); + } + + function testFakeImportant() { + $this->setMock('! foo important'); + $this->assertDef('! foo important'); + } + + function testStrip() { + $this->def = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($this->mock, false); + $this->setMock('23'); + $this->assertDef('23 ! important ', '23'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php new file mode 100644 index 000000000..9d9fc41f2 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php @@ -0,0 +1,48 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_LengthTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_Length(); + + $this->assertDef('0'); + $this->assertDef('0px'); + $this->assertDef('4.5px'); + $this->assertDef('-4.5px'); + $this->assertDef('3ex'); + $this->assertDef('3em'); + $this->assertDef('3in'); + $this->assertDef('3cm'); + $this->assertDef('3mm'); + $this->assertDef('3pt'); + $this->assertDef('3pc'); + + $this->assertDef('3PX', '3px'); + + $this->assertDef('3', false); + $this->assertDef('3miles', false); + + } + + function testNonNegative() { + + $this->def = new HTMLPurifier_AttrDef_CSS_Length('0'); + + $this->assertDef('3cm'); + $this->assertDef('-3mm', false); + + } + + function testBounding() { + $this->def = new HTMLPurifier_AttrDef_CSS_Length('-1in', '1in'); + $this->assertDef('1cm'); + $this->assertDef('-1cm'); + $this->assertDef('0'); + $this->assertDef('1em', false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php new file mode 100644 index 000000000..070066705 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php @@ -0,0 +1,35 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_ListStyleTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $config = HTMLPurifier_Config::createDefault(); + $this->def = new HTMLPurifier_AttrDef_CSS_ListStyle($config); + + $this->assertDef('lower-alpha'); + $this->assertDef('upper-roman inside'); + $this->assertDef('circle outside'); + $this->assertDef('inside'); + $this->assertDef('none'); + $this->assertDef('url("foo.gif")'); + $this->assertDef('circle url("foo.gif") inside'); + + // invalid values + $this->assertDef('outside inside', 'outside'); + + // ordering + $this->assertDef('url(foo.gif) none', 'none url("foo.gif")'); + $this->assertDef('circle lower-alpha', 'circle'); + // the spec is ambiguous about what happens in these + // cases, so we're going off the W3C CSS validator + $this->assertDef('disc none', 'disc'); + $this->assertDef('none disc', 'none'); + + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php new file mode 100644 index 000000000..4461cb508 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php @@ -0,0 +1,28 @@ +<?php + +// borrowed for the sakes of this test +class HTMLPurifier_AttrDef_CSS_MultipleTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + $this->def = new HTMLPurifier_AttrDef_CSS_Multiple( + new HTMLPurifier_AttrDef_Integer() + ); + + $this->assertDef('1 2 3 4'); + $this->assertDef('6'); + $this->assertDef('4 5'); + $this->assertDef(' 2 54 2 3', '2 54 2 3'); + $this->assertDef("6\r3", '6 3'); + + $this->assertDef('asdf', false); + $this->assertDef('a s d f', false); + $this->assertDef('1 2 3 4 5', '1 2 3 4'); + $this->assertDef('1 2 invalid 3', '1 2 3'); + + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php new file mode 100644 index 000000000..94e6ea8cf --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php @@ -0,0 +1,51 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_NumberTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_Number(); + + $this->assertDef('0'); + $this->assertDef('0.0', '0'); + $this->assertDef('1.0', '1'); + $this->assertDef('34'); + $this->assertDef('4.5'); + $this->assertDef('.5'); + $this->assertDef('0.5', '.5'); + $this->assertDef('-56.9'); + + $this->assertDef('0.', '0'); + $this->assertDef('.0', '0'); + $this->assertDef('0.0', '0'); + + $this->assertDef('1.', '1'); + $this->assertDef('.1', '.1'); + + $this->assertDef('1.0', '1'); + $this->assertDef('0.1', '.1'); + + $this->assertDef('000', '0'); + $this->assertDef(' 9', '9'); + $this->assertDef('+5.0000', '5'); + $this->assertDef('02.20', '2.2'); + $this->assertDef('2.', '2'); + + $this->assertDef('.', false); + $this->assertDef('asdf', false); + $this->assertDef('0.5.6', false); + + } + + function testNonNegative() { + + $this->def = new HTMLPurifier_AttrDef_CSS_Number(true); + $this->assertDef('23'); + $this->assertDef('-12', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php new file mode 100644 index 000000000..f712af1d2 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php @@ -0,0 +1,24 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_PercentageTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_Percentage(); + + $this->assertDef('10%'); + $this->assertDef('1.607%'); + $this->assertDef('-567%'); + + $this->assertDef(' 100% ', '100%'); + + $this->assertDef('5', false); + $this->assertDef('asdf', false); + $this->assertDef('%', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php new file mode 100644 index 000000000..dd714d206 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php @@ -0,0 +1,27 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_TextDecorationTest extends HTMLPurifier_AttrDefHarness +{ + + function testCaseInsensitive() { + + $this->def = new HTMLPurifier_AttrDef_CSS_TextDecoration(); + + $this->assertDef('none'); + $this->assertDef('none underline', 'underline'); + + $this->assertDef('underline'); + $this->assertDef('overline'); + $this->assertDef('line-through overline underline'); + $this->assertDef('overline line-through'); + $this->assertDef('UNDERLINE', 'underline'); + $this->assertDef(' underline line-through ', 'underline line-through'); + + $this->assertDef('foobar underline', 'underline'); + $this->assertDef('blink', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php new file mode 100644 index 000000000..3d6f5791e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php @@ -0,0 +1,29 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_URITest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_CSS_URI(); + + $this->assertDef('', false); + + // we could be nice but we won't be + $this->assertDef('http://www.example.com/', false); + + $this->assertDef('url(', false); + $this->assertDef('url("")', true); + $result = 'url("http://www.example.com/")'; + $this->assertDef('url(http://www.example.com/)', $result); + $this->assertDef('url("http://www.example.com/")', $result); + $this->assertDef("url('http://www.example.com/')", $result); + $this->assertDef( + ' url( "http://www.example.com/" ) ', $result); + $this->assertDef("url(http://www.example.com/foo,bar\)\'\()", + 'url("http://www.example.com/foo,bar%29%27%28")'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php new file mode 100644 index 000000000..56917aece --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php @@ -0,0 +1,164 @@ +<?php + +class HTMLPurifier_AttrDef_CSSTest extends HTMLPurifier_AttrDefHarness +{ + + function setup() { + parent::setup(); + $this->def = new HTMLPurifier_AttrDef_CSS(); + } + + function test() { + + // regular cases, singular + $this->assertDef('text-align:right;'); + $this->assertDef('border-left-style:solid;'); + $this->assertDef('border-style:solid dotted;'); + $this->assertDef('clear:right;'); + $this->assertDef('float:left;'); + $this->assertDef('font-style:italic;'); + $this->assertDef('font-variant:small-caps;'); + $this->assertDef('font-weight:bold;'); + $this->assertDef('list-style-position:outside;'); + $this->assertDef('list-style-type:upper-roman;'); + $this->assertDef('list-style:upper-roman inside;'); + $this->assertDef('text-transform:capitalize;'); + $this->assertDef('background-color:rgb(0,0,255);'); + $this->assertDef('background-color:transparent;'); + $this->assertDef('background:#333 url("chess.png") repeat fixed 50% top;'); + $this->assertDef('color:#F00;'); + $this->assertDef('border-top-color:#F00;'); + $this->assertDef('border-color:#F00 #FF0;'); + $this->assertDef('border-top-width:thin;'); + $this->assertDef('border-top-width:12px;'); + $this->assertDef('border-width:5px 1px 4px 2px;'); + $this->assertDef('border-top-width:-12px;', false); + $this->assertDef('letter-spacing:normal;'); + $this->assertDef('letter-spacing:2px;'); + $this->assertDef('word-spacing:normal;'); + $this->assertDef('word-spacing:3em;'); + $this->assertDef('font-size:200%;'); + $this->assertDef('font-size:larger;'); + $this->assertDef('font-size:12pt;'); + $this->assertDef('line-height:2;'); + $this->assertDef('line-height:2em;'); + $this->assertDef('line-height:20%;'); + $this->assertDef('line-height:normal;'); + $this->assertDef('line-height:-20%;', false); + $this->assertDef('margin-left:5px;'); + $this->assertDef('margin-right:20%;'); + $this->assertDef('margin-top:auto;'); + $this->assertDef('margin:auto 5%;'); + $this->assertDef('padding-bottom:5px;'); + $this->assertDef('padding-top:20%;'); + $this->assertDef('padding:20% 10%;'); + $this->assertDef('padding-top:-20%;', false); + $this->assertDef('text-indent:3em;'); + $this->assertDef('text-indent:5%;'); + $this->assertDef('text-indent:-3em;'); + $this->assertDef('width:50%;'); + $this->assertDef('width:50px;'); + $this->assertDef('width:auto;'); + $this->assertDef('width:-50px;', false); + $this->assertDef('text-decoration:underline;'); + $this->assertDef('font-family:sans-serif;'); + $this->assertDef("font-family:Gill, 'Times New Roman', sans-serif;"); + $this->assertDef('font:12px serif;'); + $this->assertDef('border:1px solid #000;'); + $this->assertDef('border-bottom:2em double #FF00FA;'); + $this->assertDef('border-collapse:collapse;'); + $this->assertDef('border-collapse:separate;'); + $this->assertDef('caption-side:top;'); + $this->assertDef('vertical-align:middle;'); + $this->assertDef('vertical-align:12px;'); + $this->assertDef('vertical-align:50%;'); + $this->assertDef('table-layout:fixed;'); + $this->assertDef('list-style-image:url("nice.jpg");'); + $this->assertDef('list-style:disc url("nice.jpg") inside;'); + $this->assertDef('background-image:url("foo.jpg");'); + $this->assertDef('background-image:none;'); + $this->assertDef('background-repeat:repeat-y;'); + $this->assertDef('background-attachment:fixed;'); + $this->assertDef('background-position:left 90%;'); + $this->assertDef('border-spacing:1em;'); + $this->assertDef('border-spacing:1em 2em;'); + + // duplicates + $this->assertDef('text-align:right;text-align:left;', + 'text-align:left;'); + + // a few composites + $this->assertDef('font-variant:small-caps;font-weight:900;'); + $this->assertDef('float:right;text-align:right;'); + + // selective removal + $this->assertDef('text-transform:capitalize;destroy:it;', + 'text-transform:capitalize;'); + + // inherit works for everything + $this->assertDef('text-align:inherit;'); + + // bad props + $this->assertDef('nodice:foobar;', false); + $this->assertDef('position:absolute;', false); + $this->assertDef('background-image:url(\'javascript:alert\(\)\');', false); + + // airy input + $this->assertDef(' font-weight : bold; color : #ff0000', + 'font-weight:bold;color:#ff0000;'); + + // case-insensitivity + $this->assertDef('FLOAT:LEFT;', 'float:left;'); + + // !important stripping + $this->assertDef('float:left !important;', 'float:left;'); + + } + + function testProprietary() { + $this->config->set('CSS.Proprietary', true); + + $this->assertDef('scrollbar-arrow-color:#ff0;'); + $this->assertDef('scrollbar-base-color:#ff6347;'); + $this->assertDef('scrollbar-darkshadow-color:#ffa500;'); + $this->assertDef('scrollbar-face-color:#008080;'); + $this->assertDef('scrollbar-highlight-color:#ff69b4;'); + $this->assertDef('scrollbar-shadow-color:#f0f;'); + + $this->assertDef('opacity:.2;'); + $this->assertDef('-moz-opacity:.2;'); + $this->assertDef('-khtml-opacity:.2;'); + $this->assertDef('filter:alpha(opacity=20);'); + + } + + function testImportant() { + $this->config->set('CSS.AllowImportant', true); + $this->assertDef('float:left !important;'); + } + + function testTricky() { + $this->config->set('CSS.AllowTricky', true); + $this->assertDef('display:none;'); + $this->assertDef('visibility:visible;'); + $this->assertDef('overflow:scroll;'); + } + + function testForbidden() { + $this->config->set('CSS.ForbiddenProperties', 'float'); + $this->assertDef('float:left;', false); + $this->assertDef('text-align:right;'); + } + + function testTrusted() { + $this->config->set('CSS.Trusted', true); + $this->assertDef('position:relative;'); + $this->assertDef('left:2px;'); + $this->assertDef('right:100%;'); + $this->assertDef('top:auto;'); + $this->assertDef('z-index:-2;'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php new file mode 100644 index 000000000..7722c1bc2 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php @@ -0,0 +1,37 @@ +<?php + +class HTMLPurifier_AttrDef_EnumTest extends HTMLPurifier_AttrDefHarness +{ + + function testCaseInsensitive() { + $this->def = new HTMLPurifier_AttrDef_Enum(array('one', 'two')); + $this->assertDef('one'); + $this->assertDef('ONE', 'one'); + } + + function testCaseSensitive() { + $this->def = new HTMLPurifier_AttrDef_Enum(array('one', 'two'), true); + $this->assertDef('one'); + $this->assertDef('ONE', false); + } + + function testFixing() { + $this->def = new HTMLPurifier_AttrDef_Enum(array('one')); + $this->assertDef(' one ', 'one'); + } + + function test_make() { + $factory = new HTMLPurifier_AttrDef_Enum(); + + $def = $factory->make('foo,bar'); + $def2 = new HTMLPurifier_AttrDef_Enum(array('foo', 'bar')); + $this->assertIdentical($def, $def2); + + $def = $factory->make('s:foo,BAR'); + $def2 = new HTMLPurifier_AttrDef_Enum(array('foo', 'BAR'), true); + $this->assertIdentical($def, $def2); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php new file mode 100644 index 000000000..060d0fc8f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php @@ -0,0 +1,22 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_BoolTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + $this->def = new HTMLPurifier_AttrDef_HTML_Bool('foo'); + $this->assertDef('foo'); + $this->assertDef('', false); + $this->assertDef('bar', 'foo'); + } + + function test_make() { + $factory = new HTMLPurifier_AttrDef_HTML_Bool(); + $def = $factory->make('foo'); + $def2 = new HTMLPurifier_AttrDef_HTML_Bool('foo'); + $this->assertIdentical($def, $def2); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php new file mode 100644 index 000000000..6effd3cde --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php @@ -0,0 +1,48 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_ClassTest extends HTMLPurifier_AttrDef_HTML_NmtokensTest +{ + function setUp() { + parent::setUp(); + $this->def = new HTMLPurifier_AttrDef_HTML_Class(); + } + function testAllowedClasses() { + $this->config->set('Attr.AllowedClasses', array('foo')); + $this->assertDef('foo'); + $this->assertDef('bar', false); + $this->assertDef('foo bar', 'foo'); + } + function testForbiddenClasses() { + $this->config->set('Attr.ForbiddenClasses', array('bar')); + $this->assertDef('foo'); + $this->assertDef('bar', false); + $this->assertDef('foo bar', 'foo'); + } + function testDefault() { + $this->assertDef('valid'); + $this->assertDef('a0-_'); + $this->assertDef('-valid'); + $this->assertDef('_valid'); + $this->assertDef('double valid'); + + $this->assertDef('0stillvalid'); + $this->assertDef('-0'); + + // test conditional replacement + $this->assertDef('validassoc 0valid', 'validassoc 0valid'); + + // test whitespace leniency + $this->assertDef(" double\nvalid\r", 'double valid'); + + // test case sensitivity + $this->assertDef('VALID'); + + // test duplicate removal + $this->assertDef('valid valid', 'valid'); + } + function testXHTML11Behavior() { + $this->config->set('HTML.Doctype', 'XHTML 1.1'); + $this->assertDef('0invalid', false); + $this->assertDef('valid valid', 'valid'); + } +} diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php new file mode 100644 index 000000000..8b4a46347 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php @@ -0,0 +1,20 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_ColorTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + $this->def = new HTMLPurifier_AttrDef_HTML_Color(); + $this->assertDef('', false); + $this->assertDef('foo', false); + $this->assertDef('43', false); + $this->assertDef('red', '#FF0000'); + $this->assertDef('#FF0000'); + $this->assertDef('#453443'); + $this->assertDef('453443', '#453443'); + $this->assertDef('#345', '#334455'); + $this->assertDef('120', '#112200'); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php new file mode 100644 index 000000000..7d3e24c75 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php @@ -0,0 +1,28 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_FrameTargetTest extends HTMLPurifier_AttrDefHarness +{ + + function setup() { + parent::setup(); + $this->def = new HTMLPurifier_AttrDef_HTML_FrameTarget(); + } + + function testNoneAllowed() { + $this->assertDef('', false); + $this->assertDef('foo', false); + $this->assertDef('_blank', false); + $this->assertDef('baz', false); + } + + function test() { + $this->config->set('Attr.AllowedFrameTargets', 'foo,_blank'); + $this->assertDef('', false); + $this->assertDef('foo'); + $this->assertDef('_blank'); + $this->assertDef('baz', false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php new file mode 100644 index 000000000..245db16da --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php @@ -0,0 +1,108 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_IDTest extends HTMLPurifier_AttrDefHarness +{ + + function setUp() { + parent::setUp(); + + $id_accumulator = new HTMLPurifier_IDAccumulator(); + $this->context->register('IDAccumulator', $id_accumulator); + $this->config->set('Attr.EnableID', true); + $this->def = new HTMLPurifier_AttrDef_HTML_ID(); + + } + + function test() { + + // valid ID names + $this->assertDef('alpha'); + $this->assertDef('al_ha'); + $this->assertDef('a0-:.'); + $this->assertDef('a'); + + // invalid ID names + $this->assertDef('<asa', false); + $this->assertDef('0123', false); + $this->assertDef('.asa', false); + + // test duplicate detection + $this->assertDef('once'); + $this->assertDef('once', false); + + // valid once whitespace stripped, but needs to be amended + $this->assertDef(' whee ', 'whee'); + + } + + function testPrefix() { + + $this->config->set('Attr.IDPrefix', 'user_'); + + $this->assertDef('alpha', 'user_alpha'); + $this->assertDef('<asa', false); + $this->assertDef('once', 'user_once'); + $this->assertDef('once', false); + + // if already prefixed, leave alone + $this->assertDef('user_alas'); + $this->assertDef('user_user_alas'); // how to bypass + + } + + function testTwoPrefixes() { + + $this->config->set('Attr.IDPrefix', 'user_'); + $this->config->set('Attr.IDPrefixLocal', 'story95_'); + + $this->assertDef('alpha', 'user_story95_alpha'); + $this->assertDef('<asa', false); + $this->assertDef('once', 'user_story95_once'); + $this->assertDef('once', false); + + $this->assertDef('user_story95_alas'); + $this->assertDef('user_alas', 'user_story95_user_alas'); // ! + } + + function testLocalPrefixWithoutMainPrefix() { + // no effect when IDPrefix isn't set + $this->config->set('Attr.IDPrefix', ''); + $this->config->set('Attr.IDPrefixLocal', 'story95_'); + $this->expectError('%Attr.IDPrefixLocal cannot be used unless '. + '%Attr.IDPrefix is set'); + $this->assertDef('amherst'); + + } + + // reference functionality is disabled for now + function disabled_testIDReference() { + + $this->def = new HTMLPurifier_AttrDef_HTML_ID(true); + + $this->assertDef('good_id'); + $this->assertDef('good_id'); // duplicates okay + $this->assertDef('<b>', false); + + $this->def = new HTMLPurifier_AttrDef_HTML_ID(); + + $this->assertDef('good_id'); + $this->assertDef('good_id', false); // duplicate now not okay + + $this->def = new HTMLPurifier_AttrDef_HTML_ID(true); + + $this->assertDef('good_id'); // reference still okay + + } + + function testRegexp() { + + $this->config->set('Attr.IDBlacklistRegexp', '/^g_/'); + + $this->assertDef('good_id'); + $this->assertDef('g_bad_id', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php new file mode 100644 index 000000000..d165e30b5 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php @@ -0,0 +1,32 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_LengthTest extends HTMLPurifier_AttrDef_HTML_PixelsTest +{ + + function setup() { + $this->def = new HTMLPurifier_AttrDef_HTML_Length(); + } + + function test() { + + // pixel check + parent::test(); + + // percent check + $this->assertDef('25%'); + + // Firefox maintains percent, so will we + $this->assertDef('0%'); + + // 0% <= percent <= 100% + $this->assertDef('-15%', '0%'); + $this->assertDef('120%', '100%'); + + // fractional percents, apparently, aren't allowed + $this->assertDef('56.5%', '56%'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php new file mode 100644 index 000000000..d90b65b1f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php @@ -0,0 +1,21 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_LinkTypesTest extends HTMLPurifier_AttrDefHarness +{ + + function testNull() { + + $this->def = new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'); + $this->config->set('Attr.AllowedRel', array('nofollow', 'foo')); + + $this->assertDef('', false); + $this->assertDef('nofollow', true); + $this->assertDef('nofollow foo', true); + $this->assertDef('nofollow bar', 'nofollow'); + $this->assertDef('bar', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php new file mode 100644 index 000000000..eb6f34011 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php @@ -0,0 +1,28 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_MultiLengthTest extends HTMLPurifier_AttrDef_HTML_LengthTest +{ + + function setup() { + $this->def = new HTMLPurifier_AttrDef_HTML_MultiLength(); + } + + function test() { + + // length check + parent::test(); + + $this->assertDef('*'); + $this->assertDef('1*', '*'); + $this->assertDef('56*'); + + $this->assertDef('**', false); // plain old bad + + $this->assertDef('5.4*', '5*'); // no decimals + $this->assertDef('-3*', false); // no negatives + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php new file mode 100644 index 000000000..bb64ff6e2 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php @@ -0,0 +1,35 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_NmtokensTest extends HTMLPurifier_AttrDefHarness +{ + + function setUp() { + parent::setUp(); + $this->def = new HTMLPurifier_AttrDef_HTML_Nmtokens(); + } + + function testDefault() { + + $this->assertDef('valid'); + $this->assertDef('a0-_'); + $this->assertDef('-valid'); + $this->assertDef('_valid'); + $this->assertDef('double valid'); + + $this->assertDef('0invalid', false); + $this->assertDef('-0', false); + + // test conditional replacement + $this->assertDef('validassoc 0invalid', 'validassoc'); + + // test whitespace leniency + $this->assertDef(" double\nvalid\r", 'double valid'); + + // test case sensitivity + $this->assertDef('VALID'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php new file mode 100644 index 000000000..08f25be64 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php @@ -0,0 +1,45 @@ +<?php + +class HTMLPurifier_AttrDef_HTML_PixelsTest extends HTMLPurifier_AttrDefHarness +{ + + function setup() { + $this->def = new HTMLPurifier_AttrDef_HTML_Pixels(); + } + + function test() { + + $this->assertDef('1'); + $this->assertDef('0'); + + $this->assertDef('2px', '2'); // rm px suffix + + $this->assertDef('dfs', false); // totally invalid value + + // conceivably we could repair this value, but we won't for now + $this->assertDef('9in', false); + + // test trim + $this->assertDef(' 45 ', '45'); + + // no negatives + $this->assertDef('-2', '0'); + + // remove empty + $this->assertDef('', false); + + // round down + $this->assertDef('4.9', '4'); + + } + + function test_make() { + $factory = new HTMLPurifier_AttrDef_HTML_Pixels(); + $this->def = $factory->make('30'); + $this->assertDef('25'); + $this->assertDef('35', '30'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php new file mode 100644 index 000000000..a941e31ab --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php @@ -0,0 +1,61 @@ +<?php + +class HTMLPurifier_AttrDef_IntegerTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_Integer(); + + $this->assertDef('0'); + $this->assertDef('1'); + $this->assertDef('-1'); + $this->assertDef('-10'); + $this->assertDef('14'); + $this->assertDef('+24', '24'); + $this->assertDef(' 14 ', '14'); + $this->assertDef('-0', '0'); + + $this->assertDef('-1.4', false); + $this->assertDef('3.4', false); + $this->assertDef('asdf', false); // must not return zero + $this->assertDef('2in', false); // must not return zero + + } + + function assertRange($negative, $zero, $positive) { + $this->assertDef('-100', $negative); + $this->assertDef('-1', $negative); + $this->assertDef('0', $zero); + $this->assertDef('1', $positive); + $this->assertDef('42', $positive); + } + + function testRange() { + + $this->def = new HTMLPurifier_AttrDef_Integer(false); + $this->assertRange(false, true, true); // non-negative + + $this->def = new HTMLPurifier_AttrDef_Integer(false, false); + $this->assertRange(false, false, true); // positive + + + // fringe cases + + $this->def = new HTMLPurifier_AttrDef_Integer(false, false, false); + $this->assertRange(false, false, false); // allow none + + $this->def = new HTMLPurifier_AttrDef_Integer(true, false, false); + $this->assertRange(true, false, false); // negative + + $this->def = new HTMLPurifier_AttrDef_Integer(false, true, false); + $this->assertRange(false, true, false); // zero + + $this->def = new HTMLPurifier_AttrDef_Integer(true, true, false); + $this->assertRange(true, true, false); // non-positive + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php new file mode 100644 index 000000000..c59175556 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php @@ -0,0 +1,85 @@ +<?php + +class HTMLPurifier_AttrDef_LangTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_Lang(); + + // basic good uses + $this->assertDef('en'); + $this->assertDef('en-us'); + + $this->assertDef(' en ', 'en'); // trim + $this->assertDef('EN', 'en'); // case insensitivity + + // (thanks Eugen Pankratz for noticing the typos!) + $this->assertDef('En-Us-Edison', 'en-us-edison'); // complex ci + + $this->assertDef('fr en', false); // multiple languages + $this->assertDef('%', false); // bad character + + // test overlong language according to syntax + $this->assertDef('thisistoolongsoitgetscut', false); + + // primary subtag rules + // I'm somewhat hesitant to allow x and i as primary language codes, + // because they usually are never used in real life. However, + // theoretically speaking, having them alone is permissable, so + // I'll be lenient. No XML parser is going to complain anyway. + $this->assertDef('x'); + $this->assertDef('i'); + // real world use-cases + $this->assertDef('x-klingon'); + $this->assertDef('i-mingo'); + // because the RFC only defines two and three letter primary codes, + // anything with a length of four or greater is invalid, despite + // the syntax stipulation of 1 to 8 characters. Because the RFC + // specifically states that this reservation is in order to allow + // for future versions to expand, the adoption of a new RFC will + // require these test cases to be rewritten, even if backwards- + // compatibility is largely retained (i.e. this is not forwards + // compatible) + $this->assertDef('four', false); + // for similar reasons, disallow any other one character language + $this->assertDef('f', false); + + // second subtag rules + // one letter subtags prohibited until revision. This is, however, + // less volatile than the restrictions on the primary subtags. + // Also note that this test-case tests fix-behavior: chop + // off subtags until you get a valid language code. + $this->assertDef('en-a', 'en'); + // however, x is a reserved single-letter subtag that is allowed + $this->assertDef('en-x', 'en-x'); + // 2-8 chars are permitted, but have special meaning that cannot + // be checked without maintaining country code lookup tables (for + // two characters) or special registration tables (for all above). + $this->assertDef('en-uk', true); + + // further subtag rules: only syntactic constraints + $this->assertDef('en-us-edison'); + $this->assertDef('en-us-toolonghaha', 'en-us'); + $this->assertDef('en-us-a-silly-long-one'); + + // rfc 3066 stipulates that if a three letter and a two letter code + // are available, the two letter one MUST be used. Without a language + // code lookup table, we cannot implement this functionality. + + // although the HTML protocol, technically speaking, allows you to + // omit language tags, this implicitly means that the parent element's + // language is the one applicable, which, in some cases, is incorrect. + // Thus, we allow und, only slightly defying the RFC's SHOULD NOT + // designation. + $this->assertDef('und'); + + // because attributes only allow one language, mul is allowed, complying + // with the RFC's SHOULD NOT designation. + $this->assertDef('mul'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php new file mode 100644 index 000000000..21bafe697 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php @@ -0,0 +1,34 @@ +<?php + +class HTMLPurifier_AttrDef_SwitchTest extends HTMLPurifier_AttrDefHarness +{ + + protected $with, $without; + + function setUp() { + parent::setUp(); + generate_mock_once('HTMLPurifier_AttrDef'); + $this->with = new HTMLPurifier_AttrDefMock(); + $this->without = new HTMLPurifier_AttrDefMock(); + $this->def = new HTMLPurifier_AttrDef_Switch('tag', $this->with, $this->without); + } + + function testWith() { + $token = new HTMLPurifier_Token_Start('tag'); + $this->context->register('CurrentToken', $token); + $this->with->expectOnce('validate'); + $this->with->setReturnValue('validate', 'foo'); + $this->assertDef('bar', 'foo'); + } + + function testWithout() { + $token = new HTMLPurifier_Token_Start('other-tag'); + $this->context->register('CurrentToken', $token); + $this->without->expectOnce('validate'); + $this->without->setReturnValue('validate', 'foo'); + $this->assertDef('bar', 'foo'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php new file mode 100644 index 000000000..458008aa8 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php @@ -0,0 +1,17 @@ +<?php + +class HTMLPurifier_AttrDef_TextTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_Text(); + + $this->assertDef('This is spiffy text!'); + $this->assertDef(" Casual\tCDATA parse\ncheck. ", 'Casual CDATA parse check.'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php new file mode 100644 index 000000000..c310347e9 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php @@ -0,0 +1,13 @@ +<?php + +class HTMLPurifier_AttrDef_URI_Email_SimpleCheckTest + extends HTMLPurifier_AttrDef_URI_EmailHarness +{ + + function setUp() { + $this->def = new HTMLPurifier_AttrDef_URI_Email_SimpleCheck(); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php new file mode 100644 index 000000000..594e2ce29 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php @@ -0,0 +1,31 @@ +<?php + +class HTMLPurifier_AttrDef_URI_EmailHarness extends HTMLPurifier_AttrDefHarness +{ + + /** + * Tests common email strings that are obviously pass/fail + */ + function testCore() { + $this->assertDef('bob@example.com'); + $this->assertDef(' bob@example.com ', 'bob@example.com'); + $this->assertDef('bob.thebuilder@example.net'); + $this->assertDef('Bob_the_Builder-the-2nd@example.org'); + $this->assertDef('Bob%20the%20Builder@white-space.test'); + + // extended format, with real name + //$this->assertDef('Bob%20Builder%20%3Cbobby.bob.bob@it.is.example.com%3E'); + //$this->assertDef('Bob Builder <bobby.bob.bob@it.is.example.com>'); + + // time to fail + $this->assertDef('bob', false); + $this->assertDef('bob@home@work', false); + $this->assertDef('@example.com', false); + $this->assertDef('bob@', false); + $this->assertDef('', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php new file mode 100644 index 000000000..b5827718b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php @@ -0,0 +1,53 @@ +<?php + +// takes a URI formatted host and validates it + + +class HTMLPurifier_AttrDef_URI_HostTest extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_URI_Host(); + + $this->assertDef('[2001:DB8:0:0:8:800:200C:417A]'); // IPv6 + $this->assertDef('124.15.6.89'); // IPv4 + $this->assertDef('www.google.com'); // reg-name + + // more domain name tests + $this->assertDef('test.'); + $this->assertDef('sub.test.'); + $this->assertDef('.test', false); + $this->assertDef('ff'); + $this->assertDef('1f', false); + $this->assertDef('-f', false); + $this->assertDef('f1'); + $this->assertDef('f-', false); + $this->assertDef('sub.ff'); + $this->assertDef('sub.1f', false); + $this->assertDef('sub.-f', false); + $this->assertDef('sub.f1'); + $this->assertDef('sub.f-', false); + $this->assertDef('ff.top'); + $this->assertDef('1f.top'); + $this->assertDef('-f.top', false); + $this->assertDef('ff.top'); + $this->assertDef('f1.top'); + $this->assertDef('f-.top', false); + + $this->assertDef("\xE4\xB8\xAD\xE6\x96\x87.com.cn", false); + + } + + function testIDNA() { + if (!$GLOBALS['HTMLPurifierTest']['Net_IDNA2']) { + return false; + } + $this->config->set('Core.EnableIDNA', true); + $this->assertDef("\xE4\xB8\xAD\xE6\x96\x87.com.cn", "xn--fiq228c.com.cn"); + $this->assertDef("\xe2\x80\x85.com", false); // rejected + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php new file mode 100644 index 000000000..0a4eb17ba --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php @@ -0,0 +1,25 @@ +<?php + +// IPv4 test case is spliced from Feyd's IPv6 implementation +// we ought to disallow non-routable addresses + +class HTMLPurifier_AttrDef_URI_IPv4Test extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_URI_IPv4(); + + $this->assertDef('127.0.0.1'); // standard IPv4, loopback, non-routable + $this->assertDef('0.0.0.0'); // standard IPv4, unspecified, non-routable + $this->assertDef('255.255.255.255'); // standard IPv4 + + $this->assertDef('300.0.0.0', false); // standard IPv4, out of range + $this->assertDef('124.15.6.89/60', false); // standard IPv4, prefix not allowed + + $this->assertDef('', false); // nothing + + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php new file mode 100644 index 000000000..083e818aa --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php @@ -0,0 +1,43 @@ +<?php + +// test case is from Feyd's IPv6 implementation +// we ought to disallow non-routable addresses + +class HTMLPurifier_AttrDef_URI_IPv6Test extends HTMLPurifier_AttrDefHarness +{ + + function test() { + + $this->def = new HTMLPurifier_AttrDef_URI_IPv6(); + + $this->assertDef('2001:DB8:0:0:8:800:200C:417A'); // unicast, full + $this->assertDef('FF01:0:0:0:0:0:0:101'); // multicast, full + $this->assertDef('0:0:0:0:0:0:0:1'); // loopback, full + $this->assertDef('0:0:0:0:0:0:0:0'); // unspecified, full + $this->assertDef('2001:DB8::8:800:200C:417A'); // unicast, compressed + $this->assertDef('FF01::101'); // multicast, compressed + + $this->assertDef('::1'); // loopback, compressed, non-routable + $this->assertDef('::'); // unspecified, compressed, non-routable + $this->assertDef('0:0:0:0:0:0:13.1.68.3'); // IPv4-compatible IPv6 address, full, deprecated + $this->assertDef('0:0:0:0:0:FFFF:129.144.52.38'); // IPv4-mapped IPv6 address, full + $this->assertDef('::13.1.68.3'); // IPv4-compatible IPv6 address, compressed, deprecated + $this->assertDef('::FFFF:129.144.52.38'); // IPv4-mapped IPv6 address, compressed + $this->assertDef('2001:0DB8:0000:CD30:0000:0000:0000:0000/60'); // full, with prefix + $this->assertDef('2001:0DB8::CD30:0:0:0:0/60'); // compressed, with prefix + $this->assertDef('2001:0DB8:0:CD30::/60'); // compressed, with prefix #2 + $this->assertDef('::/128'); // compressed, unspecified address type, non-routable + $this->assertDef('::1/128'); // compressed, loopback address type, non-routable + $this->assertDef('FF00::/8'); // compressed, multicast address type + $this->assertDef('FE80::/10'); // compressed, link-local unicast, non-routable + $this->assertDef('FEC0::/10'); // compressed, site-local unicast, deprecated + + $this->assertDef('2001:DB8:0:0:8:800:200C:417A:221', false); // unicast, full + $this->assertDef('FF01::101::2', false); //multicast, compressed + $this->assertDef('', false); // nothing + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php new file mode 100644 index 000000000..3044367a2 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php @@ -0,0 +1,146 @@ +<?php + +/** + * @todo Aim for complete code coverage with mocks + */ +class HTMLPurifier_AttrDef_URITest extends HTMLPurifier_AttrDefHarness +{ + + function setUp() { + $this->def = new HTMLPurifier_AttrDef_URI(); + parent::setUp(); + } + + function testIntegration() { + $this->assertDef('http://www.google.com/'); + $this->assertDef('http:', ''); + $this->assertDef('http:/foo', '/foo'); + $this->assertDef('javascript:bad_stuff();', false); + $this->assertDef('ftp://www.example.com/'); + $this->assertDef('news:rec.alt'); + $this->assertDef('nntp://news.example.com/324234'); + $this->assertDef('mailto:bob@example.com'); + } + + function testIntegrationWithPercentEncoder() { + $this->assertDef( + 'http://www.example.com/%56%fc%GJ%5%FC', + 'http://www.example.com/V%FC%25GJ%255%FC' + ); + } + + function testPercentEncoding() { + $this->assertDef( + 'http:colon:mercenary', + 'colon%3Amercenary' + ); + } + + function testPercentEncodingPreserve() { + $this->assertDef( + 'http://www.example.com/abcABC123-_.!~*()\'' + ); + } + + function testEmbeds() { + $this->def = new HTMLPurifier_AttrDef_URI(true); + $this->assertDef('http://sub.example.com/alas?foo=asd'); + $this->assertDef('mailto:foo@example.com', false); + } + + function testConfigMunge() { + $this->config->set('URI.Munge', 'http://www.google.com/url?q=%s'); + $this->assertDef( + 'http://www.example.com/', + 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F' + ); + $this->assertDef('index.html'); + $this->assertDef('javascript:foobar();', false); + } + + function testDefaultSchemeRemovedInBlank() { + $this->assertDef('http:', ''); + } + + function testDefaultSchemeRemovedInRelativeURI() { + $this->assertDef('http:/foo/bar', '/foo/bar'); + } + + function testDefaultSchemeNotRemovedInAbsoluteURI() { + $this->assertDef('http://example.com/foo/bar'); + } + + function testAltSchemeNotRemoved() { + $this->assertDef('mailto:this-looks-like-a-path@example.com'); + } + + function testResolveNullSchemeAmbiguity() { + $this->assertDef('///foo', '/foo'); + } + + function testResolveNullSchemeDoubleAmbiguity() { + $this->config->set('URI.Host', 'example.com'); + $this->assertDef('////foo', '//example.com//foo'); + } + + function testURIDefinitionValidation() { + $parser = new HTMLPurifier_URIParser(); + $uri = $parser->parse('http://example.com'); + $this->config->set('URI.DefinitionID', 'HTMLPurifier_AttrDef_URITest->testURIDefinitionValidation'); + + generate_mock_once('HTMLPurifier_URIDefinition'); + $uri_def = new HTMLPurifier_URIDefinitionMock(); + $uri_def->expectOnce('filter', array($uri, '*', '*')); + $uri_def->setReturnValue('filter', true, array($uri, '*', '*')); + $uri_def->expectOnce('postFilter', array($uri, '*', '*')); + $uri_def->setReturnValue('postFilter', true, array($uri, '*', '*')); + $uri_def->setup = true; + + // Since definitions are no longer passed by reference, we need + // to muck around with the cache to insert our mock. This is + // technically a little bad, since the cache shouldn't change + // behavior, but I don't feel too good about letting users + // overload entire definitions. + generate_mock_once('HTMLPurifier_DefinitionCache'); + $cache_mock = new HTMLPurifier_DefinitionCacheMock(); + $cache_mock->setReturnValue('get', $uri_def); + + generate_mock_once('HTMLPurifier_DefinitionCacheFactory'); + $factory_mock = new HTMLPurifier_DefinitionCacheFactoryMock(); + $old = HTMLPurifier_DefinitionCacheFactory::instance(); + HTMLPurifier_DefinitionCacheFactory::instance($factory_mock); + $factory_mock->setReturnValue('create', $cache_mock); + + $this->assertDef('http://example.com'); + + HTMLPurifier_DefinitionCacheFactory::instance($old); + } + + function test_make() { + $factory = new HTMLPurifier_AttrDef_URI(); + $def = $factory->make(''); + $def2 = new HTMLPurifier_AttrDef_URI(); + $this->assertIdentical($def, $def2); + + $def = $factory->make('embedded'); + $def2 = new HTMLPurifier_AttrDef_URI(true); + $this->assertIdentical($def, $def2); + } + + /* + function test_validate_configWhitelist() { + + $this->config->set('URI.HostPolicy', 'DenyAll'); + $this->config->set('URI.HostWhitelist', array(null, 'google.com')); + + $this->assertDef('http://example.com/fo/google.com', false); + $this->assertDef('server.txt'); + $this->assertDef('ftp://www.google.com/?t=a'); + $this->assertDef('http://google.com.tricky.spamsite.net', false); + + } + */ + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php new file mode 100644 index 000000000..b45b0ca53 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php @@ -0,0 +1,27 @@ +<?php + +class HTMLPurifier_AttrDefHarness extends HTMLPurifier_Harness +{ + + protected $def; + protected $context, $config; + + public function setUp() { + $this->config = HTMLPurifier_Config::createDefault(); + $this->context = new HTMLPurifier_Context(); + } + + // cannot be used for accumulator + function assertDef($string, $expect = true) { + // $expect can be a string or bool + $result = $this->def->validate($string, $this->config, $this->context); + if ($expect === true) { + $this->assertIdentical($string, $result); + } else { + $this->assertIdentical($expect, $result); + } + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php new file mode 100644 index 000000000..d7466e37d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php @@ -0,0 +1,32 @@ +<?php + +Mock::generatePartial( + 'HTMLPurifier_AttrDef', + 'HTMLPurifier_AttrDefTestable', + array('validate')); + +class HTMLPurifier_AttrDefTest extends HTMLPurifier_Harness +{ + + function test_parseCDATA() { + + $def = new HTMLPurifier_AttrDefTestable(); + + $this->assertIdentical('', $def->parseCDATA('')); + $this->assertIdentical('', $def->parseCDATA("\t\n\r \t\t")); + $this->assertIdentical('foo', $def->parseCDATA("\t\n\r foo\t\t")); + $this->assertIdentical('translate to space', $def->parseCDATA("translate\nto\tspace")); + + } + + function test_make() { + + $def = new HTMLPurifier_AttrDefTestable(); + $def2 = $def->make(''); + $this->assertIdentical($def, $def2); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php new file mode 100644 index 000000000..0730ab4bc --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php @@ -0,0 +1,40 @@ +<?php + +class HTMLPurifier_AttrTransform_BackgroundTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_Background(); + } + + function testEmptyInput() { + $this->assertResult( array() ); + } + + function testBasicTransform() { + $this->assertResult( + array('background' => 'logo.png'), + array('style' => 'background-image:url(logo.png);') + ); + } + + function testPrependNewCSS() { + $this->assertResult( + array('background' => 'logo.png', 'style' => 'font-weight:bold'), + array('style' => 'background-image:url(logo.png);font-weight:bold') + ); + } + + function testLenientTreatmentOfInvalidInput() { + // notice that we rely on the CSS validator later to fix this invalid + // stuff + $this->assertResult( + array('background' => 'logo.png);foo:('), + array('style' => 'background-image:url(logo.png);foo:();') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php new file mode 100644 index 000000000..cdf6f8a9b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php @@ -0,0 +1,30 @@ +<?php + +class HTMLPurifier_AttrTransform_BdoDirTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_BdoDir(); + } + + function testAddDefaultDir() { + $this->assertResult( array(), array('dir' => 'ltr') ); + } + + function testPreserveExistingDir() { + $this->assertResult( array('dir' => 'rtl') ); + } + + function testAlternateDefault() { + $this->config->set('Attr.DefaultTextDir', 'rtl'); + $this->assertResult( + array(), + array('dir' => 'rtl') + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php new file mode 100644 index 000000000..13567b74e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php @@ -0,0 +1,44 @@ +<?php + +// we currently rely on the CSS validator to fix any problems. +// This means that this transform, strictly speaking, supports +// a superset of the functionality. + +class HTMLPurifier_AttrTransform_BgColorTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_BgColor(); + } + + function testEmptyInput() { + $this->assertResult( array() ); + } + + function testBasicTransform() { + $this->assertResult( + array('bgcolor' => '#000000'), + array('style' => 'background-color:#000000;') + ); + } + + function testPrependNewCSS() { + $this->assertResult( + array('bgcolor' => '#000000', 'style' => 'font-weight:bold'), + array('style' => 'background-color:#000000;font-weight:bold') + ); + } + + function testLenientTreatmentOfInvalidInput() { + // this may change when we natively support the datatype and + // validate its contents before forwarding it on + $this->assertResult( + array('bgcolor' => '#F00'), + array('style' => 'background-color:#F00;') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php new file mode 100644 index 000000000..73f9d6b86 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php @@ -0,0 +1,38 @@ +<?php + +class HTMLPurifier_AttrTransform_BoolToCSSTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_BoolToCSS('foo', 'bar:3in;'); + } + + function testEmptyInput() { + $this->assertResult( array() ); + } + + function testBasicTransform() { + $this->assertResult( + array('foo' => 'foo'), + array('style' => 'bar:3in;') + ); + } + + function testIgnoreValueOfBooleanAttribute() { + $this->assertResult( + array('foo' => 'no'), + array('style' => 'bar:3in;') + ); + } + + function testPrependCSS() { + $this->assertResult( + array('foo' => 'foo', 'style' => 'background-color:#F00;'), + array('style' => 'bar:3in;background-color:#F00;') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php new file mode 100644 index 000000000..d10aa28e6 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php @@ -0,0 +1,38 @@ +<?php + +class HTMLPurifier_AttrTransform_BorderTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_Border(); + } + + function testEmptyInput() { + $this->assertResult( array() ); + } + + function testBasicTransform() { + $this->assertResult( + array('border' => '1'), + array('style' => 'border:1px solid;') + ); + } + + function testLenientTreatmentOfInvalidInput() { + $this->assertResult( + array('border' => '10%'), + array('style' => 'border:10%px solid;') + ); + } + + function testPrependNewCSS() { + $this->assertResult( + array('border' => '23', 'style' => 'font-weight:bold;'), + array('style' => 'border:23px solid;font-weight:bold;') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php new file mode 100644 index 000000000..f0381fe88 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php @@ -0,0 +1,73 @@ +<?php + +class HTMLPurifier_AttrTransform_EnumToCSSTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array( + 'left' => 'text-align:left;', + 'right' => 'text-align:right;' + )); + } + + function testEmptyInput() { + $this->assertResult( array() ); + } + + function testPreserveArraysWithoutInterestingAttributes() { + $this->assertResult( array('style' => 'font-weight:bold;') ); + } + + function testConvertAlignLeft() { + $this->assertResult( + array('align' => 'left'), + array('style' => 'text-align:left;') + ); + } + + function testConvertAlignRight() { + $this->assertResult( + array('align' => 'right'), + array('style' => 'text-align:right;') + ); + } + + function testRemoveInvalidAlign() { + $this->assertResult( + array('align' => 'invalid'), + array() + ); + } + + function testPrependNewCSS() { + $this->assertResult( + array('align' => 'left', 'style' => 'font-weight:bold;'), + array('style' => 'text-align:left;font-weight:bold;') + ); + + } + + function testCaseInsensitive() { + $this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array( + 'right' => 'text-align:right;' + )); + $this->assertResult( + array('align' => 'RIGHT'), + array('style' => 'text-align:right;') + ); + } + + function testCaseSensitive() { + $this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array( + 'right' => 'text-align:right;' + ), true); + $this->assertResult( + array('align' => 'RIGHT'), + array() + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php new file mode 100644 index 000000000..99f0a03e9 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php @@ -0,0 +1,55 @@ +<?php + +class HTMLPurifier_AttrTransform_ImgRequiredTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_ImgRequired(); + } + + function testAddMissingAttr() { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + array(), + array('src' => '', 'alt' => 'Invalid image') + ); + } + + function testAlternateDefaults() { + $this->config->set('Attr.DefaultInvalidImage', 'blank.png'); + $this->config->set('Attr.DefaultInvalidImageAlt', 'Pawned!'); + $this->config->set('Attr.DefaultImageAlt', 'not pawned'); + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + array(), + array('src' => 'blank.png', 'alt' => 'Pawned!') + ); + } + + function testGenerateAlt() { + $this->assertResult( + array('src' => '/path/to/foobar.png'), + array('src' => '/path/to/foobar.png', 'alt' => 'foobar.png') + ); + } + + function testAddDefaultSrc() { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + array('alt' => 'intrigue'), + array('alt' => 'intrigue', 'src' => '') + ); + } + + function testAddDefaultAlt() { + $this->config->set('Attr.DefaultImageAlt', 'default'); + $this->assertResult( + array('src' => ''), + array('src' => '', 'alt' => 'default') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php new file mode 100644 index 000000000..42e8738e4 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php @@ -0,0 +1,55 @@ +<?php + +class HTMLPurifier_AttrTransform_ImgSpaceTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_ImgSpace('vspace'); + } + + function testEmptyInput() { + $this->assertResult( array() ); + } + + function testVerticalBasicUsage() { + $this->assertResult( + array('vspace' => '1'), + array('style' => 'margin-top:1px;margin-bottom:1px;') + ); + } + + function testLenientHandlingOfInvalidInput() { + $this->assertResult( + array('vspace' => '10%'), + array('style' => 'margin-top:10%px;margin-bottom:10%px;') + ); + } + + function testPrependNewCSS() { + $this->assertResult( + array('vspace' => '23', 'style' => 'font-weight:bold;'), + array('style' => 'margin-top:23px;margin-bottom:23px;font-weight:bold;') + ); + } + + function testHorizontalBasicUsage() { + $this->obj = new HTMLPurifier_AttrTransform_ImgSpace('hspace'); + $this->assertResult( + array('hspace' => '1'), + array('style' => 'margin-left:1px;margin-right:1px;') + ); + } + + function testInvalidConstructionParameter() { + $this->expectError('ispace is not valid space attribute'); + $this->obj = new HTMLPurifier_AttrTransform_ImgSpace('ispace'); + $this->assertResult( + array('ispace' => '1'), + array() + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php new file mode 100644 index 000000000..2603ff1be --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php @@ -0,0 +1,94 @@ +<?php + +class HTMLPurifier_AttrTransform_InputTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_Input(); + } + + function testEmptyInput() { + $this->assertResult(array()); + } + + function testInvalidCheckedWithEmpty() { + $this->assertResult(array('checked' => 'checked'), array()); + } + + function testInvalidCheckedWithPassword() { + $this->assertResult(array( + 'checked' => 'checked', + 'type' => 'password' + ), array( + 'type' => 'password' + )); + } + + function testValidCheckedWithUcCheckbox() { + $this->assertResult(array( + 'checked' => 'checked', + 'type' => 'CHECKBOX', + 'value' => 'bar', + )); + } + + function testInvalidMaxlength() { + $this->assertResult(array( + 'maxlength' => '10', + 'type' => 'checkbox', + 'value' => 'foo', + ), array( + 'type' => 'checkbox', + 'value' => 'foo', + )); + } + + function testValidMaxLength() { + $this->assertResult(array( + 'maxlength' => '10', + )); + } + + // these two are really bad test-cases + + function testSizeWithCheckbox() { + $this->assertResult(array( + 'type' => 'checkbox', + 'value' => 'foo', + 'size' => '100px', + ), array( + 'type' => 'checkbox', + 'value' => 'foo', + 'size' => '100', + )); + } + + function testSizeWithText() { + $this->assertResult(array( + 'type' => 'password', + 'size' => '100px', // spurious value, to indicate no validation takes place + ), array( + 'type' => 'password', + 'size' => '100px', + )); + } + + function testInvalidSrc() { + $this->assertResult(array( + 'src' => 'img.png', + ), array()); + } + + function testMissingValue() { + $this->assertResult(array( + 'type' => 'checkbox', + ), array( + 'type' => 'checkbox', + 'value' => '', + )); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php new file mode 100644 index 000000000..960ad20a0 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php @@ -0,0 +1,46 @@ +<?php + +class HTMLPurifier_AttrTransform_LangTest + extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_Lang(); + } + + function testEmptyInput() { + $this->assertResult(array()); + } + + function testCopyLangToXMLLang() { + $this->assertResult( + array('lang' => 'en'), + array('lang' => 'en', 'xml:lang' => 'en') + ); + } + + function testPreserveAttributes() { + $this->assertResult( + array('src' => 'vert.png', 'lang' => 'fr'), + array('src' => 'vert.png', 'lang' => 'fr', 'xml:lang' => 'fr') + ); + } + + function testCopyXMLLangToLang() { + $this->assertResult( + array('xml:lang' => 'en'), + array('xml:lang' => 'en', 'lang' => 'en') + ); + } + + function testXMLLangOverridesLang() { + $this->assertResult( + array('lang' => 'fr', 'xml:lang' => 'de'), + array('lang' => 'de', 'xml:lang' => 'de') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php new file mode 100644 index 000000000..3fbaa0ccf --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php @@ -0,0 +1,45 @@ +<?php + +class HTMLPurifier_AttrTransform_LengthTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_Length('width'); + } + + function testEmptyInput() { + $this->assertResult( array() ); + } + + function testTransformPixel() { + $this->assertResult( + array('width' => '10'), + array('style' => 'width:10px;') + ); + } + + function testTransformPercentage() { + $this->assertResult( + array('width' => '10%'), + array('style' => 'width:10%;') + ); + } + + function testPrependNewCSS() { + $this->assertResult( + array('width' => '10%', 'style' => 'font-weight:bold'), + array('style' => 'width:10%;font-weight:bold') + ); + } + + function testLenientTreatmentOfInvalidInput() { + $this->assertResult( + array('width' => 'asdf'), + array('style' => 'width:asdf;') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php new file mode 100644 index 000000000..bae4a8d03 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php @@ -0,0 +1,40 @@ +<?php + +class HTMLPurifier_AttrTransform_NameSyncTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_NameSync(); + $this->accumulator = new HTMLPurifier_IDAccumulator(); + $this->context->register('IDAccumulator', $this->accumulator); + $this->config->set('Attr.EnableID', true); + } + + function testEmpty() { + $this->assertResult( array() ); + } + + function testAllowSame() { + $this->assertResult( + array('name' => 'free', 'id' => 'free') + ); + } + + function testAllowDifferent() { + $this->assertResult( + array('name' => 'tryit', 'id' => 'thisgood') + ); + } + + function testCheckName() { + $this->accumulator->add('notok'); + $this->assertResult( + array('name' => 'notok', 'id' => 'ok'), + array('id' => 'ok') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php new file mode 100644 index 000000000..10e121238 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php @@ -0,0 +1,31 @@ +<?php + +class HTMLPurifier_AttrTransform_NameTest extends HTMLPurifier_AttrTransformHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_AttrTransform_Name(); + } + + function testEmpty() { + $this->assertResult( array() ); + } + + function testTransformNameToID() { + $this->assertResult( + array('name' => 'free'), + array('id' => 'free') + ); + } + + function testExistingIDOverridesName() { + $this->assertResult( + array('name' => 'tryit', 'id' => 'tobad'), + array('id' => 'tobad') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php new file mode 100644 index 000000000..d43c0108f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php @@ -0,0 +1,13 @@ +<?php + +class HTMLPurifier_AttrTransformHarness extends HTMLPurifier_ComplexHarness +{ + + public function setUp() { + parent::setUp(); + $this->func = 'transform'; + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php new file mode 100644 index 000000000..71a788580 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php @@ -0,0 +1,45 @@ +<?php + +Mock::generatePartial( + 'HTMLPurifier_AttrTransform', + 'HTMLPurifier_AttrTransformTestable', + array('transform')); + +class HTMLPurifier_AttrTransformTest extends HTMLPurifier_Harness +{ + + function test_prependCSS() { + + $t = new HTMLPurifier_AttrTransformTestable(); + + $attr = array(); + $t->prependCSS($attr, 'style:new;'); + $this->assertIdentical(array('style' => 'style:new;'), $attr); + + $attr = array('style' => 'style:original;'); + $t->prependCSS($attr, 'style:new;'); + $this->assertIdentical(array('style' => 'style:new;style:original;'), $attr); + + $attr = array('style' => 'style:original;', 'misc' => 'un-related'); + $t->prependCSS($attr, 'style:new;'); + $this->assertIdentical(array('style' => 'style:new;style:original;', 'misc' => 'un-related'), $attr); + + } + + function test_confiscateAttr() { + + $t = new HTMLPurifier_AttrTransformTestable(); + + $attr = array('flavor' => 'sweet'); + $this->assertIdentical('sweet', $t->confiscateAttr($attr, 'flavor')); + $this->assertIdentical(array(), $attr); + + $attr = array('flavor' => 'sweet'); + $this->assertIdentical(null, $t->confiscateAttr($attr, 'color')); + $this->assertIdentical(array('flavor' => 'sweet'), $attr); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php new file mode 100644 index 000000000..d1ae43709 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php @@ -0,0 +1,26 @@ +<?php + +class HTMLPurifier_AttrTypesTest extends HTMLPurifier_Harness +{ + + function test_get() { + $types = new HTMLPurifier_AttrTypes(); + + $this->assertIdentical( + $types->get('CDATA'), + new HTMLPurifier_AttrDef_Text() + ); + + $this->expectError('Cannot retrieve undefined attribute type foobar'); + $types->get('foobar'); + + $this->assertIdentical( + $types->get('Enum#foo,bar'), + new HTMLPurifier_AttrDef_Enum(array('foo', 'bar')) + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php new file mode 100644 index 000000000..307d3292b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php @@ -0,0 +1,66 @@ +<?php + +class HTMLPurifier_AttrValidator_ErrorsTest extends HTMLPurifier_ErrorsHarness +{ + + public function setup() { + parent::setup(); + $config = HTMLPurifier_Config::createDefault(); + $this->language = HTMLPurifier_LanguageFactory::instance()->create($config, $this->context); + $this->context->register('Locale', $this->language); + $this->collector = new HTMLPurifier_ErrorCollector($this->context); + $this->context->register('Generator', new HTMLPurifier_Generator($config, $this->context)); + } + + protected function invoke($input) { + $validator = new HTMLPurifier_AttrValidator(); + $validator->validateToken($input, $this->config, $this->context); + } + + function testAttributesTransformedGlobalPre() { + $def = $this->config->getHTMLDefinition(true); + generate_mock_once('HTMLPurifier_AttrTransform'); + $transform = new HTMLPurifier_AttrTransformMock(); + $input = array('original' => 'value'); + $output = array('class' => 'value'); // must be valid + $transform->setReturnValue('transform', $output, array($input, new AnythingExpectation(), new AnythingExpectation())); + $def->info_attr_transform_pre[] = $transform; + + $token = new HTMLPurifier_Token_Start('span', $input, 1); + $this->invoke($token); + + $result = $this->collector->getRaw(); + $expect = array( + array(1, E_NOTICE, 'Attributes on <span> transformed from original to class', array()), + ); + $this->assertIdentical($result, $expect); + } + + function testAttributesTransformedLocalPre() { + $this->config->set('HTML.TidyLevel', 'heavy'); + $input = array('align' => 'right'); + $output = array('style' => 'text-align:right;'); + $token = new HTMLPurifier_Token_Start('p', $input, 1); + $this->invoke($token); + $result = $this->collector->getRaw(); + $expect = array( + array(1, E_NOTICE, 'Attributes on <p> transformed from align to style', array()), + ); + $this->assertIdentical($result, $expect); + } + + // too lazy to check for global post and global pre + + function testAttributeRemoved() { + $token = new HTMLPurifier_Token_Start('p', array('foobar' => 'right'), 1); + $this->invoke($token); + $result = $this->collector->getRaw(); + $expect = array( + array(1, E_ERROR, 'foobar attribute on <p> removed', array()), + ); + $this->assertIdentical($result, $expect); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php new file mode 100644 index 000000000..82493f40e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php @@ -0,0 +1,40 @@ +<?php + +class HTMLPurifier_ChildDef_ChameleonTest extends HTMLPurifier_ChildDefHarness +{ + + protected $isInline; + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_ChildDef_Chameleon( + 'b | i', // allowed only when in inline context + 'b | i | div' // allowed only when in block context + ); + $this->context->register('IsInline', $this->isInline); + } + + function testInlineAlwaysAllowed() { + $this->isInline = true; + $this->assertResult( + '<b>Allowed.</b>' + ); + } + + function testBlockNotAllowedInInline() { + $this->isInline = true; + $this->assertResult( + '<div>Not allowed.</div>', '' + ); + } + + function testBlockAllowedInNonInline() { + $this->isInline = false; + $this->assertResult( + '<div>Allowed.</div>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php new file mode 100644 index 000000000..5b138a3c1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php @@ -0,0 +1,89 @@ +<?php + +class HTMLPurifier_ChildDef_CustomTest extends HTMLPurifier_ChildDefHarness +{ + + function test() { + + $this->obj = new HTMLPurifier_ChildDef_Custom('(a,b?,c*,d+,(a,b)*)'); + + $this->assertEqual($this->obj->elements, array('a' => true, + 'b' => true, 'c' => true, 'd' => true)); + + $this->assertResult('', false); + $this->assertResult('<a /><a />', false); + + $this->assertResult('<a /><b /><c /><d /><a /><b />'); + $this->assertResult('<a /><d>Dob</d><a /><b>foo</b>'. + '<a href="moo" /><b>foo</b>'); + + } + + function testNesting() { + $this->obj = new HTMLPurifier_ChildDef_Custom('(a,b,(c|d))+'); + $this->assertEqual($this->obj->elements, array('a' => true, + 'b' => true, 'c' => true, 'd' => true)); + $this->assertResult('', false); + $this->assertResult('<a /><b /><c /><a /><b /><d />'); + $this->assertResult('<a /><b /><c /><d />', false); + } + + function testNestedEitherOr() { + $this->obj = new HTMLPurifier_ChildDef_Custom('b,(a|(c|d))+'); + $this->assertEqual($this->obj->elements, array('a' => true, + 'b' => true, 'c' => true, 'd' => true)); + $this->assertResult('', false); + $this->assertResult('<b /><a /><c /><d />'); + $this->assertResult('<b /><d /><a /><a />'); + $this->assertResult('<b /><a />'); + $this->assertResult('<acd />', false); + } + + function testNestedQuantifier() { + $this->obj = new HTMLPurifier_ChildDef_Custom('(b,c+)*'); + $this->assertEqual($this->obj->elements, array('b' => true, 'c' => true)); + $this->assertResult(''); + $this->assertResult('<b /><c />'); + $this->assertResult('<b /><c /><c /><c />'); + $this->assertResult('<b /><c /><b /><c />'); + $this->assertResult('<b /><c /><b />', false); + } + + function testEitherOr() { + + $this->obj = new HTMLPurifier_ChildDef_Custom('a|b'); + $this->assertEqual($this->obj->elements, array('a' => true, 'b' => true)); + $this->assertResult('', false); + $this->assertResult('<a />'); + $this->assertResult('<b />'); + $this->assertResult('<a /><b />', false); + + } + + function testCommafication() { + + $this->obj = new HTMLPurifier_ChildDef_Custom('a,b'); + $this->assertEqual($this->obj->elements, array('a' => true, 'b' => true)); + $this->assertResult('<a /><b />'); + $this->assertResult('<ab />', false); + + } + + function testPcdata() { + $this->obj = new HTMLPurifier_ChildDef_Custom('#PCDATA,a'); + $this->assertEqual($this->obj->elements, array('#PCDATA' => true, 'a' => true)); + $this->assertResult('foo<a />'); + $this->assertResult('<a />', false); + } + + function testWhitespace() { + $this->obj = new HTMLPurifier_ChildDef_Custom('a'); + $this->assertEqual($this->obj->elements, array('a' => true)); + $this->assertResult('foo<a />', false); + $this->assertResult('<a />'); + $this->assertResult(' <a />'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php new file mode 100644 index 000000000..02dcab0fb --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php @@ -0,0 +1,50 @@ +<?php + +class HTMLPurifier_ChildDef_ListTest extends HTMLPurifier_ChildDefHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_ChildDef_List(); + } + + function testEmptyInput() { + $this->assertResult('', false); + } + + function testSingleLi() { + $this->assertResult('<li />'); + } + + function testSomeLi() { + $this->assertResult('<li>asdf</li><li />'); + } + + function testIllegal() { + // XXX actually this never gets triggered in practice + $this->assertResult('<li /><b />', '<li /><li><b /></li>'); + } + + function testOlAtBeginning() { + $this->assertResult('<ol />', '<li><ol /></li>'); + } + + function testOlAtBeginningWithOtherJunk() { + $this->assertResult('<ol /><li />', '<li><ol /></li><li />'); + } + + function testOlInMiddle() { + $this->assertResult('<li>Foo</li><ol><li>Bar</li></ol>', '<li>Foo<ol><li>Bar</li></ol></li>'); + } + + function testMultipleOl() { + $this->assertResult('<li /><ol /><ol />', '<li><ol /><ol /></li>'); + } + + function testUlAtBeginning() { + $this->assertResult('<ul />', '<li><ul /></li>'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/OptionalTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/OptionalTest.php new file mode 100644 index 000000000..a5f34f7b1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/OptionalTest.php @@ -0,0 +1,33 @@ +<?php + +class HTMLPurifier_ChildDef_OptionalTest extends HTMLPurifier_ChildDefHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_ChildDef_Optional('b | i'); + } + + function testBasicUsage() { + $this->assertResult('<b>Bold text</b><img />', '<b>Bold text</b>'); + } + + function testRemoveForbiddenText() { + $this->assertResult('Not allowed text', ''); + } + + function testEmpty() { + $this->assertResult(''); + } + + function testWhitespace() { + $this->assertResult(' '); + } + + function testMultipleWhitespace() { + $this->assertResult(' '); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/RequiredTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/RequiredTest.php new file mode 100644 index 000000000..8bb4f45ee --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/RequiredTest.php @@ -0,0 +1,73 @@ +<?php + +class HTMLPurifier_ChildDef_RequiredTest extends HTMLPurifier_ChildDefHarness +{ + + function testPrepareString() { + $def = new HTMLPurifier_ChildDef_Required('foobar | bang |gizmo'); + $this->assertIdentical($def->elements, + array( + 'foobar' => true + ,'bang' => true + ,'gizmo' => true + )); + } + + function testPrepareArray() { + $def = new HTMLPurifier_ChildDef_Required(array('href', 'src')); + $this->assertIdentical($def->elements, + array( + 'href' => true + ,'src' => true + )); + } + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_ChildDef_Required('dt | dd'); + } + + function testEmptyInput() { + $this->assertResult('', false); + } + + function testRemoveIllegalTagsAndElements() { + $this->assertResult( + '<dt>Term</dt>Text in an illegal location'. + '<dd>Definition</dd><b>Illegal tag</b>', + '<dt>Term</dt><dd>Definition</dd>'); + $this->assertResult('How do you do!', false); + } + + function testIgnoreWhitespace() { + // whitespace shouldn't trigger it + $this->assertResult("\n<dd>Definition</dd> "); + } + + function testPreserveWhitespaceAfterRemoval() { + $this->assertResult( + '<dd>Definition</dd> <b></b> ', + '<dd>Definition</dd> ' + ); + } + + function testDeleteNodeIfOnlyWhitespace() { + $this->assertResult("\t ", false); + } + + function testPCDATAAllowed() { + $this->obj = new HTMLPurifier_ChildDef_Required('#PCDATA | b'); + $this->assertResult('Out <b>Bold text</b><img />', 'Out <b>Bold text</b>'); + } + + function testPCDATAAllowedWithEscaping() { + $this->obj = new HTMLPurifier_ChildDef_Required('#PCDATA | b'); + $this->config->set('Core.EscapeInvalidChildren', true); + $this->assertResult( + 'Out <b>Bold text</b><img />', + 'Out <b>Bold text</b><img />' + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/StrictBlockquoteTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/StrictBlockquoteTest.php new file mode 100644 index 000000000..52594b1a0 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/StrictBlockquoteTest.php @@ -0,0 +1,83 @@ +<?php + +class HTMLPurifier_ChildDef_StrictBlockquoteTest +extends HTMLPurifier_ChildDefHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_ChildDef_StrictBlockquote('div | p'); + } + + function testEmptyInput() { + $this->assertResult(''); + } + + function testPreserveValidP() { + $this->assertResult('<p>Valid</p>'); + } + + function testPreserveValidDiv() { + $this->assertResult('<div>Still valid</div>'); + } + + function testWrapTextWithP() { + $this->assertResult('Needs wrap', '<p>Needs wrap</p>'); + } + + function testNoWrapForWhitespaceOrValidElements() { + $this->assertResult('<p>Do not wrap</p> <p>Whitespace</p>'); + } + + function testWrapTextNextToValidElements() { + $this->assertResult( + 'Wrap'. '<p>Do not wrap</p>', + '<p>Wrap</p><p>Do not wrap</p>' + ); + } + + function testWrapInlineElements() { + $this->assertResult( + '<p>Do not</p>'.'<b>Wrap</b>', + '<p>Do not</p><p><b>Wrap</b></p>' + ); + } + + function testWrapAndRemoveInvalidTags() { + $this->assertResult( + '<li>Not allowed</li>Paragraph.<p>Hmm.</p>', + '<p>Not allowedParagraph.</p><p>Hmm.</p>' + ); + } + + function testWrapComplicatedSring() { + $this->assertResult( + $var = 'He said<br />perhaps<br />we should <b>nuke</b> them.', + "<p>$var</p>" + ); + } + + function testWrapAndRemoveInvalidTagsComplex() { + $this->assertResult( + '<foo>Bar</foo><bas /><b>People</b>Conniving.'. '<p>Fools!</p>', + '<p>Bar'. '<b>People</b>Conniving.</p><p>Fools!</p>' + ); + } + + function testAlternateWrapper() { + $this->config->set('HTML.BlockWrapper', 'div'); + $this->assertResult('Needs wrap', '<div>Needs wrap</div>'); + + } + + function testError() { + $this->expectError('Cannot use non-block element as block wrapper'); + $this->obj = new HTMLPurifier_ChildDef_StrictBlockquote('div | p'); + $this->config->set('HTML.BlockWrapper', 'dav'); + $this->config->set('Cache.DefinitionImpl', null); + $this->assertResult('Needs wrap', '<p>Needs wrap</p>'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/TableTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/TableTest.php new file mode 100644 index 000000000..2f72d187a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDef/TableTest.php @@ -0,0 +1,75 @@ +<?php + +// we're using empty tags to compact the tests: under real circumstances +// there would be contents in them + +class HTMLPurifier_ChildDef_TableTest extends HTMLPurifier_ChildDefHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_ChildDef_Table(); + } + + function testEmptyInput() { + $this->assertResult('', false); + } + + function testSingleRow() { + $this->assertResult('<tr />'); + } + + function testComplexContents() { + $this->assertResult('<caption /><col /><thead /><tfoot /><tbody>'. + '<tr><td>asdf</td></tr></tbody>'); + $this->assertResult('<col /><col /><col /><tr />'); + } + + function testReorderContents() { + $this->assertResult( + '<col /><colgroup /><tbody /><tfoot /><thead /><tr>1</tr><caption /><tr />', + '<caption /><col /><colgroup /><thead /><tfoot /><tbody /><tbody><tr>1</tr><tr /></tbody>'); + } + + function testXhtml11Illegal() { + $this->assertResult( + '<thead><tr><th>a</th></tr></thead><tr><td>a</td></tr>', + '<thead><tr><th>a</th></tr></thead><tbody><tr><td>a</td></tr></tbody>' + ); + } + + function testTrOverflowAndClose() { + $this->assertResult( + '<tr><td>a</td></tr><tr><td>b</td></tr><tbody><tr><td>c</td></tr></tbody><tr><td>d</td></tr>', + '<tbody><tr><td>a</td></tr><tr><td>b</td></tr></tbody><tbody><tr><td>c</td></tr></tbody><tbody><tr><td>d</td></tr></tbody>' + ); + } + + function testDuplicateProcessing() { + $this->assertResult( + '<caption>1</caption><caption /><tbody /><tbody /><tfoot>1</tfoot><tfoot />', + '<caption>1</caption><tfoot>1</tfoot><tbody /><tbody /><tbody />' + ); + } + + function testRemoveText() { + $this->assertResult('foo', false); + } + + function testStickyWhitespaceOnTr() { + $this->config->set('Output.Newline', "\n"); + $this->assertResult("\n <tr />\n <tr />\n "); + } + + function testStickyWhitespaceOnTSection() { + $this->config->set('Output.Newline', "\n"); + $this->assertResult( + "\n\t<tbody />\n\t\t<tfoot />\n\t\t\t", + "\n\t\t<tfoot />\n\t<tbody />\n\t\t\t" + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ChildDefHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/ChildDefHarness.php new file mode 100644 index 000000000..4b4d2f5b6 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ChildDefHarness.php @@ -0,0 +1,16 @@ +<?php + +class HTMLPurifier_ChildDefHarness extends HTMLPurifier_ComplexHarness +{ + + public function setUp() { + parent::setUp(); + $this->obj = null; + $this->func = 'validateChildren'; + $this->to_tokens = true; + $this->to_html = true; + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ComplexHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/ComplexHarness.php new file mode 100644 index 000000000..8e806c63c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ComplexHarness.php @@ -0,0 +1,106 @@ +<?php + +/** + * General-purpose test-harness that makes testing functions that require + * configuration and context objects easier when those two parameters are + * meaningless. See HTMLPurifier_ChildDefTest for a good example of usage. + */ +class HTMLPurifier_ComplexHarness extends HTMLPurifier_Harness +{ + + /** + * Instance of the object that will execute the method + */ + protected $obj; + + /** + * Name of the function to be executed + */ + protected $func; + + /** + * Whether or not the method deals in tokens. If set to true, assertResult() + * will transparently convert HTML to and back from tokens. + */ + protected $to_tokens = false; + + /** + * Whether or not to convert tokens back into HTML before performing + * equality check, has no effect on bools. + */ + protected $to_html = false; + + /** + * Instance of an HTMLPurifier_Lexer implementation. + */ + protected $lexer; + + public function __construct() { + $this->lexer = new HTMLPurifier_Lexer_DirectLex(); + parent::__construct(); + } + + /** + * Asserts a specific result from a one parameter + config/context function + * @param $input Input parameter + * @param $expect Expectation + * @param $config Configuration array in form of Ns.Directive => Value. + * Has no effect if $this->config is set. + * @param $context_array Context array in form of Key => Value or an actual + * context object. + */ + protected function assertResult($input, $expect = true) { + + if ($this->to_tokens && is_string($input)) { + // $func may cause $input to change, so "clone" another copy + // to sacrifice + $input = $this->tokenize($temp = $input); + $input_c = $this->tokenize($temp); + } else { + $input_c = $input; + } + + // call the function + $func = $this->func; + $result = $this->obj->$func($input_c, $this->config, $this->context); + + // test a bool result + if (is_bool($result)) { + $this->assertIdentical($expect, $result); + return; + } elseif (is_bool($expect)) { + $expect = $input; + } + + if ($this->to_html) { + $result = $this->generate($result); + if (is_array($expect)) { + $expect = $this->generate($expect); + } + } + $this->assertIdentical($expect, $result); + + if ($expect !== $result) { + echo '<pre>' . var_dump($result) . '</pre>'; + } + + } + + /** + * Tokenize HTML into tokens, uses member variables for common variables + */ + protected function tokenize($html) { + return $this->lexer->tokenizeHTML($html, $this->config, $this->context); + } + + /** + * Generate textual HTML from tokens + */ + protected function generate($tokens) { + $generator = new HTMLPurifier_Generator($this->config, $this->context); + return $generator->generateFromTokens($tokens); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/InterchangeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/InterchangeTest.php new file mode 100644 index 000000000..0d539ea2c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/InterchangeTest.php @@ -0,0 +1,21 @@ +<?php + +class HTMLPurifier_ConfigSchema_InterchangeTest extends UnitTestCase +{ + + protected $interchange; + + public function setup() { + $this->interchange = new HTMLPurifier_ConfigSchema_Interchange(); + } + + function testAddDirective() { + $v = new HTMLPurifier_ConfigSchema_Interchange_Directive(); + $v->id = new HTMLPurifier_ConfigSchema_Interchange_Id('Namespace.Directive'); + $this->interchange->addDirective($v); + $this->assertIdentical($v, $this->interchange->directives['Namespace.Directive']); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesAliasCollision.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesAliasCollision.vtest new file mode 100644 index 000000000..ed3857c40 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesAliasCollision.vtest @@ -0,0 +1,13 @@ +ERROR: Alias 'Ns.BothWantThisName' in aliases in directive 'Ns.Dir2' collides with alias for directive 'Ns.Dir' +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: int +DEFAULT: 3 +ALIASES: Ns.BothWantThisName +---- +Ns.Dir2 +DESCRIPTION: Directive +TYPE: string +DEFAULT: 'a' +ALIASES: Ns.BothWantThisName diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesDirectiveCollision.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesDirectiveCollision.vtest new file mode 100644 index 000000000..582a481a4 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/aliasesDirectiveCollision.vtest @@ -0,0 +1,12 @@ +ERROR: Alias 'Ns.Innocent' in aliases in directive 'Ns.Dir' collides with another directive +---- +Ns.Innocent +DESCRIPTION: Innocent directive +TYPE: int +DEFAULT: 3 +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: string +DEFAULT: 'a' +ALIASES: Ns.Innocent diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedIsString.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedIsString.vtest new file mode 100644 index 000000000..9ecc5f930 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedIsString.vtest @@ -0,0 +1,7 @@ +ERROR: Value 3 in allowed in directive 'Ns.Dir' must be a string +---- +ID: Ns.Dir +TYPE: string +DESCRIPTION: Description +DEFAULT: 'asdf' +ALLOWED: 'asdf', 3 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedNotEmpty.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedNotEmpty.vtest new file mode 100644 index 000000000..1e08fab0d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/allowedNotEmpty.vtest @@ -0,0 +1,7 @@ +ERROR: Allowed in directive 'Ns.Dir' must not be empty +---- +ID: Ns.Dir +TYPE: string +DESCRIPTION: Description +DEFAULT: 'asdf' +ALLOWED: diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultIsAllowed.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultIsAllowed.vtest new file mode 100644 index 000000000..ef1944707 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultIsAllowed.vtest @@ -0,0 +1,7 @@ +ERROR: Default in directive 'Ns.Dir' must be an allowed value +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: string +DEFAULT: 'a' +ALLOWED: 'b' diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultNullWithAllowed.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultNullWithAllowed.vtest new file mode 100644 index 000000000..7ba3c85c0 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultNullWithAllowed.vtest @@ -0,0 +1,5 @@ +Ns.Dir +DESCRIPTION: Directive +TYPE: string/null +DEFAULT: null +ALLOWED: 'a' diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultType.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultType.vtest new file mode 100644 index 000000000..79ea043ac --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/defaultType.vtest @@ -0,0 +1,6 @@ +ERROR: Expected type string, got integer in DEFAULT in directive hash 'Ns.Dir' +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: string +DEFAULT: 0 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest new file mode 100644 index 000000000..d09ddc77b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/descriptionNotEmpty.vtest @@ -0,0 +1,5 @@ +ERROR: Description in directive 'Ns.Dir' must not be empty +---- +Ns.Dir +TYPE: int +DEFAULT: 0 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/ignoreNamespace.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/ignoreNamespace.vtest new file mode 100644 index 000000000..3fc28d3a4 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/ignoreNamespace.vtest @@ -0,0 +1,3 @@ +Ns +DESCRIPTION: Namespace + diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeDefined.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeDefined.vtest new file mode 100644 index 000000000..4bb8abeb2 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeDefined.vtest @@ -0,0 +1,5 @@ +ERROR: TYPE in directive hash 'Ns.Dir' not defined +---- +Ns.Dir +DESCRIPTION: Notice that TYPE is missing +DEFAULT: 0 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest new file mode 100644 index 000000000..f9851f536 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeExists.vtest @@ -0,0 +1,6 @@ +ERROR: Invalid type 'foobar' in DEFAULT in directive hash 'Ns.Dir' +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: foobar +DEFAULT: 0 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithAllowedIsStringType.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithAllowedIsStringType.vtest new file mode 100644 index 000000000..bf4e2b21b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithAllowedIsStringType.vtest @@ -0,0 +1,7 @@ +ERROR: Type in directive 'Ns.Dir' must be a string type when used with allowed or value aliases +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: int +DEFAULT: 3 +ALLOWED: 1, 2, 3 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithValueAliasesIsStringType.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithValueAliasesIsStringType.vtest new file mode 100644 index 000000000..59cf4333a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/typeWithValueAliasesIsStringType.vtest @@ -0,0 +1,7 @@ +ERROR: Type in directive 'Ns.Dir' must be a string type when used with allowed or value aliases +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: int +DEFAULT: 3 +VALUE-ALIASES: 2 => 3 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest new file mode 100644 index 000000000..c83649e8d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/unique.vtest @@ -0,0 +1,11 @@ +ERROR: Cannot redefine directive 'Ns.Dir' +---- +ID: Ns.Dir +DESCRIPTION: Version 1 +TYPE: int +DEFAULT: 0 +---- +ID: Ns.Dir +DESCRIPTION: Version 2 +TYPE: int +DEFAULT: 0 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasIsString.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasIsString.vtest new file mode 100644 index 000000000..b9f28eee1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasIsString.vtest @@ -0,0 +1,7 @@ +ERROR: Alias 3 in valueAliases in directive 'Ns.Dir' must be a string +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: string +DEFAULT: 'a' +VALUE-ALIASES: 3 => 'a' diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasNotAllowed.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasNotAllowed.vtest new file mode 100644 index 000000000..ef9c14883 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesAliasNotAllowed.vtest @@ -0,0 +1,8 @@ +ERROR: Alias 'b' in valueAliases in directive 'Ns.Dir' must not be an allowed value +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: string +DEFAULT: 'a' +ALLOWED: 'a', 'b', 'c' +VALUE-ALIASES: 'b' => 'c' diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesNotAliasSelf.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesNotAliasSelf.vtest new file mode 100644 index 000000000..4c417fd5f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesNotAliasSelf.vtest @@ -0,0 +1,7 @@ +ERROR: Alias 'bar' in valueAliases in directive 'Ns.Dir' must not be an alias to itself +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: string +DEFAULT: 'foo' +VALUE-ALIASES: 'bar' => 'bar' diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealAllowed.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealAllowed.vtest new file mode 100644 index 000000000..89502d34a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealAllowed.vtest @@ -0,0 +1,8 @@ +ERROR: Alias 'c' in valueAliases in directive 'Ns.Dir' must be an alias to an allowed value +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: string +DEFAULT: 'a' +ALLOWED: 'a', 'b' +VALUE-ALIASES: 'c' => 'd' diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealIsString.vtest b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealIsString.vtest new file mode 100644 index 000000000..92ec197dc --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/Validator/directive/valueAliasesRealIsString.vtest @@ -0,0 +1,7 @@ +ERROR: Alias target 3 from alias 'b' in valueAliases in directive 'Ns.Dir' must be a string +---- +Ns.Dir +DESCRIPTION: Directive +TYPE: string +DEFAULT: 'a' +VALUE-ALIASES: 'b' => 3 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php new file mode 100644 index 000000000..38625136e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorAtomTest.php @@ -0,0 +1,92 @@ +<?php + +class HTMLPurifier_ConfigSchema_ValidatorAtomTest extends UnitTestCase +{ + + protected function expectValidationException($msg) { + $this->expectException(new HTMLPurifier_ConfigSchema_Exception($msg)); + } + + protected function makeAtom($value) { + $obj = new stdClass(); + $obj->property = $value; + // Note that 'property' and 'context' are magic wildcard values + return new HTMLPurifier_ConfigSchema_ValidatorAtom('context', $obj, 'property'); + } + + function testAssertIsString() { + $this->makeAtom('foo')->assertIsString(); + } + + function testAssertIsStringFail() { + $this->expectValidationException("Property in context must be a string"); + $this->makeAtom(3)->assertIsString(); + } + + function testAssertNotNull() { + $this->makeAtom('foo')->assertNotNull(); + } + + function testAssertNotNullFail() { + $this->expectValidationException("Property in context must not be null"); + $this->makeAtom(null)->assertNotNull(); + } + + function testAssertAlnum() { + $this->makeAtom('foo2')->assertAlnum(); + } + + function testAssertAlnumFail() { + $this->expectValidationException("Property in context must be alphanumeric"); + $this->makeAtom('%a')->assertAlnum(); + } + + function testAssertAlnumFailIsString() { + $this->expectValidationException("Property in context must be a string"); + $this->makeAtom(3)->assertAlnum(); + } + + function testAssertNotEmpty() { + $this->makeAtom('foo')->assertNotEmpty(); + } + + function testAssertNotEmptyFail() { + $this->expectValidationException("Property in context must not be empty"); + $this->makeAtom('')->assertNotEmpty(); + } + + function testAssertIsBool() { + $this->makeAtom(false)->assertIsBool(); + } + + function testAssertIsBoolFail() { + $this->expectValidationException("Property in context must be a boolean"); + $this->makeAtom('0')->assertIsBool(); + } + + function testAssertIsArray() { + $this->makeAtom(array())->assertIsArray(); + } + + function testAssertIsArrayFail() { + $this->expectValidationException("Property in context must be an array"); + $this->makeAtom('asdf')->assertIsArray(); + } + + + function testAssertIsLookup() { + $this->makeAtom(array('foo' => true))->assertIsLookup(); + } + + function testAssertIsLookupFail() { + $this->expectValidationException("Property in context must be a lookup array"); + $this->makeAtom(array('foo' => 4))->assertIsLookup(); + } + + function testAssertIsLookupFailIsArray() { + $this->expectValidationException("Property in context must be an array"); + $this->makeAtom('asdf')->assertIsLookup(); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php new file mode 100644 index 000000000..9cbf36e2d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTest.php @@ -0,0 +1,101 @@ +<?php + +/** + * Special test-case for cases that can't be tested using + * HTMLPurifier_ConfigSchema_ValidatorTestCase. + */ +class HTMLPurifier_ConfigSchema_ValidatorTest extends UnitTestCase +{ + public $validator, $interchange; + + public function setup() { + $this->validator = new HTMLPurifier_ConfigSchema_Validator(); + $this->interchange = new HTMLPurifier_ConfigSchema_Interchange(); + } + + function testDirectiveIntegrityViolation() { + $d = $this->makeDirective('Ns.Dir'); + $d->id = new HTMLPurifier_ConfigSchema_Interchange_Id('Ns.Dir2'); + $this->expectValidationException("Integrity violation: key 'Ns.Dir' does not match internal id 'Ns.Dir2'"); + $this->validator->validate($this->interchange); + } + + function testDirectiveTypeNotEmpty() { + $d = $this->makeDirective('Ns.Dir'); + $d->default = 0; + $d->description = 'Description'; + + $this->expectValidationException("Type in directive 'Ns.Dir' must not be empty"); + $this->validator->validate($this->interchange); + } + + function testDirectiveDefaultInvalid() { + $d = $this->makeDirective('Ns.Dir'); + $d->default = 'asdf'; + $d->type = 'int'; + $d->description = 'Description'; + + $this->expectValidationException("Default in directive 'Ns.Dir' had error: Expected type int, got string"); + $this->validator->validate($this->interchange); + } + + function testDirectiveIdIsString() { + $d = $this->makeDirective(3); + $d->default = 0; + $d->type = 'int'; + $d->description = 'Description'; + + $this->expectValidationException("Key in id '3' in directive '3' must be a string"); + $this->validator->validate($this->interchange); + } + + function testDirectiveTypeAllowsNullIsBool() { + $d = $this->makeDirective('Ns.Dir'); + $d->default = 0; + $d->type = 'int'; + $d->description = 'Description'; + $d->typeAllowsNull = 'yes'; + + $this->expectValidationException("TypeAllowsNull in directive 'Ns.Dir' must be a boolean"); + $this->validator->validate($this->interchange); + } + + function testDirectiveValueAliasesIsArray() { + $d = $this->makeDirective('Ns.Dir'); + $d->default = 'a'; + $d->type = 'string'; + $d->description = 'Description'; + $d->valueAliases = 2; + + $this->expectValidationException("ValueAliases in directive 'Ns.Dir' must be an array"); + $this->validator->validate($this->interchange); + } + + function testDirectiveAllowedIsLookup() { + $d = $this->makeDirective('Ns.Dir'); + $d->default = 'foo'; + $d->type = 'string'; + $d->description = 'Description'; + $d->allowed = array('foo' => 1); + + $this->expectValidationException("Allowed in directive 'Ns.Dir' must be a lookup array"); + $this->validator->validate($this->interchange); + } + + // helper functions + + + protected function makeDirective($key) { + $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive(); + $directive->id = new HTMLPurifier_ConfigSchema_Interchange_Id($key); + $this->interchange->addDirective($directive); + return $directive; + } + + protected function expectValidationException($msg) { + $this->expectException(new HTMLPurifier_ConfigSchema_Exception($msg)); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php new file mode 100644 index 000000000..87fa14d0d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchema/ValidatorTestCase.php @@ -0,0 +1,44 @@ +<?php + +/** + * Controller for validator test-cases. + */ +class HTMLPurifier_ConfigSchema_ValidatorTestCase extends UnitTestCase +{ + + protected $_path, $_parser, $_builder; + public $validator; + + public function __construct($path) { + $this->_path = $path; + $this->_parser = new HTMLPurifier_StringHashParser(); + $this->_builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); + parent::__construct($path); + } + + public function setup() { + $this->validator = new HTMLPurifier_ConfigSchema_Validator(); + } + + function testValidator() { + $hashes = $this->_parser->parseMultiFile($this->_path); + $interchange = new HTMLPurifier_ConfigSchema_Interchange(); + $error = null; + foreach ($hashes as $hash) { + if (!isset($hash['ID'])) { + if (isset($hash['ERROR'])) { + $this->expectException( + new HTMLPurifier_ConfigSchema_Exception($hash['ERROR']) + ); + } + continue; + } + $this->_builder->build($interchange, new HTMLPurifier_StringHash($hash)); + } + $this->validator->validate($interchange); + $this->pass(); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchemaTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchemaTest.php new file mode 100644 index 000000000..dc3bf99eb --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigSchemaTest.php @@ -0,0 +1,99 @@ +<?php + +class HTMLPurifier_ConfigSchemaTest extends HTMLPurifier_Harness +{ + + protected $schema; + + public function setup() { + $this->schema = new HTMLPurifier_ConfigSchema(); + } + + function test_define() { + $this->schema->add('Car.Seats', 5, 'int', false); + + $this->assertIdentical($this->schema->defaults['Car.Seats'], 5); + $this->assertIdentical($this->schema->info['Car.Seats']->type, HTMLPurifier_VarParser::INT); + + $this->schema->add('Car.Age', null, 'int', true); + + $this->assertIdentical($this->schema->defaults['Car.Age'], null); + $this->assertIdentical($this->schema->info['Car.Age']->type, HTMLPurifier_VarParser::INT); + + } + + function test_defineAllowedValues() { + $this->schema->add('QuantumNumber.Spin', 0.5, 'float', false); + $this->schema->add('QuantumNumber.Current', 's', 'string', false); + $this->schema->add('QuantumNumber.Difficulty', null, 'string', true); + + $this->schema->addAllowedValues( // okay, since default is null + 'QuantumNumber.Difficulty', array('easy' => true, 'medium' => true, 'hard' => true) + ); + + $this->assertIdentical($this->schema->defaults['QuantumNumber.Difficulty'], null); + $this->assertIdentical($this->schema->info['QuantumNumber.Difficulty']->type, HTMLPurifier_VarParser::STRING); + $this->assertIdentical($this->schema->info['QuantumNumber.Difficulty']->allow_null, true); + $this->assertIdentical($this->schema->info['QuantumNumber.Difficulty']->allowed, + array( + 'easy' => true, + 'medium' => true, + 'hard' => true + ) + ); + + } + + function test_defineValueAliases() { + $this->schema->add('Abbrev.HTH', 'Happy to Help', 'string', false); + $this->schema->addAllowedValues( + 'Abbrev.HTH', array( + 'Happy to Help' => true, + 'Hope that Helps' => true, + 'HAIL THE HAND!' => true, + ) + ); + $this->schema->addValueAliases( + 'Abbrev.HTH', array( + 'happy' => 'Happy to Help', + 'hope' => 'Hope that Helps' + ) + ); + $this->schema->addValueAliases( // delayed addition + 'Abbrev.HTH', array( + 'hail' => 'HAIL THE HAND!' + ) + ); + + $this->assertIdentical($this->schema->defaults['Abbrev.HTH'], 'Happy to Help'); + $this->assertIdentical($this->schema->info['Abbrev.HTH']->type, HTMLPurifier_VarParser::STRING); + $this->assertIdentical($this->schema->info['Abbrev.HTH']->allowed, + array( + 'Happy to Help' => true, + 'Hope that Helps' => true, + 'HAIL THE HAND!' => true + ) + ); + $this->assertIdentical($this->schema->info['Abbrev.HTH']->aliases, + array( + 'happy' => 'Happy to Help', + 'hope' => 'Hope that Helps', + 'hail' => 'HAIL THE HAND!' + ) + ); + + } + + function testAlias() { + $this->schema->add('Home.Rug', 3, 'int', false); + $this->schema->addAlias('Home.Carpet', 'Home.Rug'); + + $this->assertTrue(!isset($this->schema->defaults['Home.Carpet'])); + $this->assertIdentical($this->schema->info['Home.Carpet']->key, 'Home.Rug'); + $this->assertIdentical($this->schema->info['Home.Carpet']->isAlias, true); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-create.ini b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-create.ini new file mode 100644 index 000000000..31412f99a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-create.ini @@ -0,0 +1,4 @@ +[Cake] +Sprinkles = 42 + +; vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-finalize.ini b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-finalize.ini new file mode 100644 index 000000000..3ab185fad --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-finalize.ini @@ -0,0 +1,4 @@ +[Poem] +Meter = alexandrine + +; vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-loadIni.ini b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-loadIni.ini new file mode 100644 index 000000000..cfa5a907f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest-loadIni.ini @@ -0,0 +1,6 @@ +[Shortcut] +Copy = q +Cut = t +Paste = p + +; vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest.php new file mode 100644 index 000000000..3d36fc057 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ConfigTest.php @@ -0,0 +1,557 @@ +<?php + +class HTMLPurifier_ConfigTest extends HTMLPurifier_Harness +{ + + protected $schema; + protected $oldFactory; + + public function setUp() { + // set up a dummy schema object for testing + $this->schema = new HTMLPurifier_ConfigSchema(); + } + + // test functionality based on ConfigSchema + + function testNormal() { + $this->schema->add('Element.Abbr', 'H', 'string', false); + $this->schema->add('Element.Name', 'hydrogen', 'istring', false); + $this->schema->add('Element.Number', 1, 'int', false); + $this->schema->add('Element.Mass', 1.00794, 'float', false); + $this->schema->add('Element.Radioactive', false, 'bool', false); + $this->schema->add('Element.Isotopes', array(1 => true, 2 => true, 3 => true), 'lookup', false); + $this->schema->add('Element.Traits', array('nonmetallic', 'odorless', 'flammable'), 'list', false); + $this->schema->add('Element.IsotopeNames', array(1 => 'protium', 2 => 'deuterium', 3 => 'tritium'), 'hash', false); + $this->schema->add('Element.Object', new stdClass(), 'mixed', false); + + $config = new HTMLPurifier_Config($this->schema); + $config->autoFinalize = false; + $config->chatty = false; + + // test default value retrieval + $this->assertIdentical($config->get('Element.Abbr'), 'H'); + $this->assertIdentical($config->get('Element.Name'), 'hydrogen'); + $this->assertIdentical($config->get('Element.Number'), 1); + $this->assertIdentical($config->get('Element.Mass'), 1.00794); + $this->assertIdentical($config->get('Element.Radioactive'), false); + $this->assertIdentical($config->get('Element.Isotopes'), array(1 => true, 2 => true, 3 => true)); + $this->assertIdentical($config->get('Element.Traits'), array('nonmetallic', 'odorless', 'flammable')); + $this->assertIdentical($config->get('Element.IsotopeNames'), array(1 => 'protium', 2 => 'deuterium', 3 => 'tritium')); + $this->assertIdentical($config->get('Element.Object'), new stdClass()); + + // test setting values + $config->set('Element.Abbr', 'Pu'); + $config->set('Element.Name', 'PLUTONIUM'); // test decaps + $config->set('Element.Number', '94'); // test parsing + $config->set('Element.Mass', '244.'); // test parsing + $config->set('Element.Radioactive', true); + $config->set('Element.Isotopes', array(238, 239)); // test inversion + $config->set('Element.Traits', 'nuclear, heavy, actinide'); // test parsing + $config->set('Element.IsotopeNames', array(238 => 'Plutonium-238', 239 => 'Plutonium-239')); + $config->set('Element.Object', false); // unmodeled + + $this->expectError('Cannot set undefined directive Element.Metal to value'); + $config->set('Element.Metal', true); + + $this->expectError('Value for Element.Radioactive is of invalid type, should be bool'); + $config->set('Element.Radioactive', 'very'); + + // test value retrieval + $this->assertIdentical($config->get('Element.Abbr'), 'Pu'); + $this->assertIdentical($config->get('Element.Name'), 'plutonium'); + $this->assertIdentical($config->get('Element.Number'), 94); + $this->assertIdentical($config->get('Element.Mass'), 244.); + $this->assertIdentical($config->get('Element.Radioactive'), true); + $this->assertIdentical($config->get('Element.Isotopes'), array(238 => true, 239 => true)); + $this->assertIdentical($config->get('Element.Traits'), array('nuclear', 'heavy', 'actinide')); + $this->assertIdentical($config->get('Element.IsotopeNames'), array(238 => 'Plutonium-238', 239 => 'Plutonium-239')); + $this->assertIdentical($config->get('Element.Object'), false); + + $this->expectError('Cannot retrieve value of undefined directive Element.Metal'); + $config->get('Element.Metal'); + + } + + function testEnumerated() { + + // case sensitive + $this->schema->add('Instrument.Manufacturer', 'Yamaha', 'string', false); + $this->schema->addAllowedValues('Instrument.Manufacturer', array( + 'Yamaha' => true, 'Conn-Selmer' => true, 'Vandoren' => true, + 'Laubin' => true, 'Buffet' => true, 'other' => true)); + $this->schema->addValueAliases('Instrument.Manufacturer', array( + 'Selmer' => 'Conn-Selmer')); + + // case insensitive + $this->schema->add('Instrument.Family', 'woodwind', 'istring', false); + $this->schema->addAllowedValues('Instrument.Family', array( + 'brass' => true, 'woodwind' => true, 'percussion' => true, + 'string' => true, 'keyboard' => true, 'electronic' => true)); + $this->schema->addValueAliases('Instrument.Family', array( + 'synth' => 'electronic')); + + $config = new HTMLPurifier_Config($this->schema); + $config->autoFinalize = false; + $config->chatty = false; + + // case sensitive + + $config->set('Instrument.Manufacturer', 'Vandoren'); + $this->assertIdentical($config->get('Instrument.Manufacturer'), 'Vandoren'); + + $config->set('Instrument.Manufacturer', 'Selmer'); + $this->assertIdentical($config->get('Instrument.Manufacturer'), 'Conn-Selmer'); + + $this->expectError('Value not supported, valid values are: Yamaha, Conn-Selmer, Vandoren, Laubin, Buffet, other'); + $config->set('Instrument.Manufacturer', 'buffet'); + + // case insensitive + + $config->set('Instrument.Family', 'brass'); + $this->assertIdentical($config->get('Instrument.Family'), 'brass'); + + $config->set('Instrument.Family', 'PERCUSSION'); + $this->assertIdentical($config->get('Instrument.Family'), 'percussion'); + + $config->set('Instrument.Family', 'synth'); + $this->assertIdentical($config->get('Instrument.Family'), 'electronic'); + + $config->set('Instrument.Family', 'Synth'); + $this->assertIdentical($config->get('Instrument.Family'), 'electronic'); + + } + + function testNull() { + + $this->schema->add('ReportCard.English', null, 'string', true); + $this->schema->add('ReportCard.Absences', 0, 'int', false); + + $config = new HTMLPurifier_Config($this->schema); + $config->autoFinalize = false; + $config->chatty = false; + + $config->set('ReportCard.English', 'B-'); + $this->assertIdentical($config->get('ReportCard.English'), 'B-'); + + $config->set('ReportCard.English', null); // not yet graded + $this->assertIdentical($config->get('ReportCard.English'), null); + + // error + $this->expectError('Value for ReportCard.Absences is of invalid type, should be int'); + $config->set('ReportCard.Absences', null); + + } + + function testAliases() { + + $this->schema->add('Home.Rug', 3, 'int', false); + $this->schema->addAlias('Home.Carpet', 'Home.Rug'); + + $config = new HTMLPurifier_Config($this->schema); + $config->autoFinalize = false; + $config->chatty = false; + + $this->assertIdentical($config->get('Home.Rug'), 3); + + $this->expectError('Cannot get value from aliased directive, use real name Home.Rug'); + $config->get('Home.Carpet'); + + $this->expectError('Home.Carpet is an alias, preferred directive name is Home.Rug'); + $config->set('Home.Carpet', 999); + $this->assertIdentical($config->get('Home.Rug'), 999); + + } + + // test functionality based on method + + function test_getBatch() { + + $this->schema->add('Variables.TangentialAcceleration', 'a_tan', 'string', false); + $this->schema->add('Variables.AngularAcceleration', 'alpha', 'string', false); + + $config = new HTMLPurifier_Config($this->schema); + $config->autoFinalize = false; + $config->chatty = false; + + // grab a namespace + $this->assertIdentical( + $config->getBatch('Variables'), + array( + 'TangentialAcceleration' => 'a_tan', + 'AngularAcceleration' => 'alpha' + ) + ); + + // grab a non-existant namespace + $this->expectError('Cannot retrieve undefined namespace Constants'); + $config->getBatch('Constants'); + + } + + function test_loadIni() { + + $this->schema->add('Shortcut.Copy', 'c', 'istring', false); + $this->schema->add('Shortcut.Paste', 'v', 'istring', false); + $this->schema->add('Shortcut.Cut', 'x', 'istring', false); + + $config = new HTMLPurifier_Config($this->schema); + $config->autoFinalize = false; + + $config->loadIni(dirname(__FILE__) . '/ConfigTest-loadIni.ini'); + + $this->assertIdentical($config->get('Shortcut.Copy'), 'q'); + $this->assertIdentical($config->get('Shortcut.Paste'), 'p'); + $this->assertIdentical($config->get('Shortcut.Cut'), 't'); + + } + + function test_getHTMLDefinition() { + + // we actually want to use the old copy, because the definition + // generation routines have dependencies on configuration values + + $config = HTMLPurifier_Config::createDefault(); + $config->set('HTML.Doctype', 'XHTML 1.0 Strict'); + $config->autoFinalize = false; + + $def = $config->getCSSDefinition(); + $this->assertIsA($def, 'HTMLPurifier_CSSDefinition'); + + $def = $config->getHTMLDefinition(); + $def2 = $config->getHTMLDefinition(); + $this->assertIsA($def, 'HTMLPurifier_HTMLDefinition'); + $this->assertTrue($def === $def2); + $this->assertTrue($def->setup); + + $old_def = clone $def2; + + $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); + $def = $config->getHTMLDefinition(); + $this->assertIsA($def, 'HTMLPurifier_HTMLDefinition'); + $this->assertTrue($def !== $old_def); + $this->assertTrue($def->setup); + + } + + function test_getHTMLDefinition_deprecatedRawError() { + $config = HTMLPurifier_Config::createDefault(); + $config->chatty = false; + // test deprecated retrieval of raw definition + $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->test_getHTMLDefinition()'); + $config->set('HTML.DefinitionRev', 3); + $this->expectError("Useless DefinitionID declaration"); + $def = $config->getHTMLDefinition(true); + $this->assertEqual(false, $def->setup); + + // auto initialization + $config->getHTMLDefinition(); + $this->assertTrue($def->setup); + } + + function test_getHTMLDefinition_optimizedRawError() { + $this->expectException(new HTMLPurifier_Exception("Cannot set optimized = true when raw = false")); + $config = HTMLPurifier_Config::createDefault(); + $config->getHTMLDefinition(false, true); + } + + function test_getHTMLDefinition_rawAfterSetupError() { + $this->expectException(new HTMLPurifier_Exception("Cannot retrieve raw definition after it has already been setup")); + $config = HTMLPurifier_Config::createDefault(); + $config->chatty = false; + $config->getHTMLDefinition(); + $config->getHTMLDefinition(true); + } + + function test_getHTMLDefinition_inconsistentOptimizedError() { + $this->expectError("Useless DefinitionID declaration"); + $this->expectException(new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals")); + $config = HTMLPurifier_Config::create(array('HTML.DefinitionID' => 'HTMLPurifier_ConfigTest->test_getHTMLDefinition_inconsistentOptimizedError')); + $config->chatty = false; + $config->getHTMLDefinition(true, false); + $config->getHTMLDefinition(true, true); + } + + function test_getHTMLDefinition_inconsistentOptimizedError2() { + $this->expectException(new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals")); + $config = HTMLPurifier_Config::create(array('HTML.DefinitionID' => 'HTMLPurifier_ConfigTest->test_getHTMLDefinition_inconsistentOptimizedError2')); + $config->chatty = false; + $config->getHTMLDefinition(true, true); + $config->getHTMLDefinition(true, false); + } + + function test_getHTMLDefinition_rawError() { + $config = HTMLPurifier_Config::createDefault(); + $this->expectException(new HTMLPurifier_Exception('Cannot retrieve raw version without specifying %HTML.DefinitionID')); + $def = $config->getHTMLDefinition(true, true); + } + + function test_getCSSDefinition() { + $config = HTMLPurifier_Config::createDefault(); + $def = $config->getCSSDefinition(); + $this->assertIsA($def, 'HTMLPurifier_CSSDefinition'); + } + + function test_getDefinition() { + $this->schema->add('Cache.DefinitionImpl', null, 'string', true); + $config = new HTMLPurifier_Config($this->schema); + $this->expectException(new HTMLPurifier_Exception("Definition of Crust type not supported")); + $config->getDefinition('Crust'); + } + + function test_loadArray() { + // setup a few dummy namespaces/directives for our testing + $this->schema->add('Zoo.Aadvark', 0, 'int', false); + $this->schema->add('Zoo.Boar', 0, 'int', false); + $this->schema->add('Zoo.Camel', 0, 'int', false); + $this->schema->add('Zoo.Others', array(), 'list', false); + + $config_manual = new HTMLPurifier_Config($this->schema); + $config_loadabbr = new HTMLPurifier_Config($this->schema); + $config_loadfull = new HTMLPurifier_Config($this->schema); + + $config_manual->set('Zoo.Aadvark', 3); + $config_manual->set('Zoo.Boar', 5); + $config_manual->set('Zoo.Camel', 2000); // that's a lotta camels! + $config_manual->set('Zoo.Others', array('Peacock', 'Dodo')); // wtf! + + // condensed form + $config_loadabbr->loadArray(array( + 'Zoo.Aadvark' => 3, + 'Zoo.Boar' => 5, + 'Zoo.Camel' => 2000, + 'Zoo.Others' => array('Peacock', 'Dodo') + )); + + // fully expanded form + $config_loadfull->loadArray(array( + 'Zoo' => array( + 'Aadvark' => 3, + 'Boar' => 5, + 'Camel' => 2000, + 'Others' => array('Peacock', 'Dodo') + ) + )); + + $this->assertIdentical($config_manual, $config_loadabbr); + $this->assertIdentical($config_manual, $config_loadfull); + + } + + function test_create() { + + $this->schema->add('Cake.Sprinkles', 666, 'int', false); + $this->schema->add('Cake.Flavor', 'vanilla', 'string', false); + + $config = new HTMLPurifier_Config($this->schema); + $config->set('Cake.Sprinkles', 42); + + // test flat pass-through + $created_config = HTMLPurifier_Config::create($config, $this->schema); + $this->assertIdentical($config, $created_config); + + // test loadArray + $created_config = HTMLPurifier_Config::create(array('Cake.Sprinkles' => 42), $this->schema); + $this->assertIdentical($config, $created_config); + + // test loadIni + $created_config = HTMLPurifier_Config::create(dirname(__FILE__) . '/ConfigTest-create.ini', $this->schema); + $this->assertIdentical($config, $created_config); + + } + + function test_finalize() { + + // test finalization + + $this->schema->add('Poem.Meter', 'iambic', 'string', false); + + $config = new HTMLPurifier_Config($this->schema); + $config->autoFinalize = false; + $config->chatty = false; + + $config->set('Poem.Meter', 'irregular'); + + $config->finalize(); + + $this->expectError('Cannot set directive after finalization'); + $config->set('Poem.Meter', 'vedic'); + + $this->expectError('Cannot load directives after finalization'); + $config->loadArray(array('Poem.Meter' => 'octosyllable')); + + $this->expectError('Cannot load directives after finalization'); + $config->loadIni(dirname(__FILE__) . '/ConfigTest-finalize.ini'); + + } + + function test_loadArrayFromForm() { + + $this->schema->add('Pancake.Mix', 'buttermilk', 'string', false); + $this->schema->add('Pancake.Served', true, 'bool', false); + $this->schema->add('Toppings.Syrup', true, 'bool', false); + $this->schema->add('Toppings.Flavor', 'maple', 'string', false); + $this->schema->add('Toppings.Strawberries', 3, 'int', false); + $this->schema->add('Toppings.Calories', 2000, 'int', true); + $this->schema->add('Toppings.DefinitionID', null, 'string', true); + $this->schema->add('Toppings.DefinitionRev', 1, 'int', false); + $this->schema->add('Toppings.Protected', 1, 'int', false); + + $get = array( + 'breakfast' => array( + 'Pancake.Mix' => 'nasty', + 'Pancake.Served' => '0', + 'Toppings.Syrup' => '0', + 'Toppings.Flavor' => "juice", + 'Toppings.Strawberries' => '999', + 'Toppings.Calories' => '', + 'Null_Toppings.Calories' => '1', + 'Toppings.DefinitionID' => '<argh>', + 'Toppings.DefinitionRev' => '65', + 'Toppings.Protected' => '4', + ) + ); + + $config_expect = HTMLPurifier_Config::create(array( + 'Pancake.Served' => false, + 'Toppings.Syrup' => false, + 'Toppings.Flavor' => "juice", + 'Toppings.Strawberries' => 999, + 'Toppings.Calories' => null + ), $this->schema); + + $config_result = HTMLPurifier_Config::loadArrayFromForm( + $get, 'breakfast', + array('Pancake.Served', 'Toppings', '-Toppings.Protected'), + false, // mq fix + $this->schema + ); + + $this->assertEqual($config_expect, $config_result); + + /* + MAGIC QUOTES NOT TESTED!!! + + $get = array( + 'breakfast' => array( + 'Pancake.Mix' => 'n\\asty' + ) + ); + $config_expect = HTMLPurifier_Config::create(array( + 'Pancake.Mix' => 'n\\asty' + )); + $config_result = HTMLPurifier_Config::loadArrayFromForm($get, 'breakfast', true, false); + $this->assertEqual($config_expect, $config_result); + */ + } + + function test_getAllowedDirectivesForForm() { + $this->schema->add('Unused.Unused', 'Foobar', 'string', false); + $this->schema->add('Partial.Allowed', true, 'bool', false); + $this->schema->add('Partial.Unused', 'Foobar', 'string', false); + $this->schema->add('All.Allowed', true, 'bool', false); + $this->schema->add('All.Blacklisted', 'Foobar', 'string', false); // explicitly blacklisted + $this->schema->add('All.DefinitionID', 'Foobar', 'string', true); // auto-blacklisted + $this->schema->add('All.DefinitionRev', 2, 'int', false); // auto-blacklisted + + $input = array('Partial.Allowed', 'All', '-All.Blacklisted'); + $output = HTMLPurifier_Config::getAllowedDirectivesForForm($input, $this->schema); + $expect = array( + array('Partial', 'Allowed'), + array('All', 'Allowed') + ); + + $this->assertEqual($output, $expect); + + } + + function testDeprecatedAPI() { + $this->schema->add('Foo.Bar', 2, 'int', false); + $config = new HTMLPurifier_Config($this->schema); + $config->chatty = false; + $this->expectError('Using deprecated API: use $config->set(\'Foo.Bar\', ...) instead'); + $config->set('Foo', 'Bar', 4); + $this->expectError('Using deprecated API: use $config->get(\'Foo.Bar\') instead'); + $this->assertIdentical($config->get('Foo', 'Bar'), 4); + } + + function testInherit() { + $this->schema->add('Phantom.Masked', 25, 'int', false); + $this->schema->add('Phantom.Unmasked', 89, 'int', false); + $this->schema->add('Phantom.Latemasked', 11, 'int', false); + $config = new HTMLPurifier_Config($this->schema); + $config->set('Phantom.Masked', 800); + $subconfig = HTMLPurifier_Config::inherit($config); + $config->set('Phantom.Latemasked', 100, 'int', false); + $this->assertIdentical($subconfig->get('Phantom.Masked'), 800); + $this->assertIdentical($subconfig->get('Phantom.Unmasked'), 89); + $this->assertIdentical($subconfig->get('Phantom.Latemasked'), 100); + } + + function testSerialize() { + $config = HTMLPurifier_Config::createDefault(); + $config->set('HTML.Allowed', 'a'); + $config2 = unserialize($config->serialize()); + $this->assertIdentical($config->get('HTML.Allowed'), $config2->get('HTML.Allowed')); + } + + function testDefinitionCachingNothing() { + list($mock, $config) = $this->setupCacheMock('HTML'); + // should not touch the cache + $mock->expectNever('get'); + $mock->expectNever('add'); + $mock->expectNever('set'); + $config->getDefinition('HTML', true); + $config->getDefinition('HTML', true); + $config->getDefinition('HTML'); + $this->teardownCacheMock(); + } + + function testDefinitionCachingOptimized() { + list($mock, $config) = $this->setupCacheMock('HTML'); + $mock->expectNever('set'); + $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->testDefinitionCachingOptimized'); + $mock->expectOnce('get'); + $mock->setReturnValue('get', null); + $this->assertTrue($config->maybeGetRawHTMLDefinition()); + $this->assertTrue($config->maybeGetRawHTMLDefinition()); + $mock->expectOnce('add'); + $config->getDefinition('HTML'); + $this->teardownCacheMock(); + } + + function testDefinitionCachingOptimizedHit() { + $fake_config = HTMLPurifier_Config::createDefault(); + $fake_def = $fake_config->getHTMLDefinition(); + list($mock, $config) = $this->setupCacheMock('HTML'); + // should never frob cache + $mock->expectNever('add'); + $mock->expectNever('set'); + $config->set('HTML.DefinitionID', 'HTMLPurifier_ConfigTest->testDefinitionCachingOptimizedHit'); + $mock->expectOnce('get'); + $mock->setReturnValue('get', $fake_def); + $this->assertNull($config->maybeGetRawHTMLDefinition()); + $config->getDefinition('HTML'); + $config->getDefinition('HTML'); + $this->teardownCacheMock(); + } + + protected function setupCacheMock($type) { + // inject our definition cache mock globally (borrowed from + // DefinitionFactoryTest) + generate_mock_once("HTMLPurifier_DefinitionCacheFactory"); + $factory = new HTMLPurifier_DefinitionCacheFactoryMock(); + $this->oldFactory = HTMLPurifier_DefinitionCacheFactory::instance(); + HTMLPurifier_DefinitionCacheFactory::instance($factory); + generate_mock_once("HTMLPurifier_DefinitionCache"); + $mock = new HTMLPurifier_DefinitionCacheMock(); + $config = HTMLPurifier_Config::createDefault(); + $factory->setReturnValue('create', $mock, array($type, $config)); + return array($mock, $config); + } + protected function teardownCacheMock() { + HTMLPurifier_DefinitionCacheFactory::instance($this->oldFactory); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ContextTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ContextTest.php new file mode 100644 index 000000000..c5cef1651 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ContextTest.php @@ -0,0 +1,84 @@ +<?php + +// mocks +class HTMLPurifier_ContextTest extends HTMLPurifier_Harness +{ + + protected $context; + + public function setUp() { + $this->context = new HTMLPurifier_Context(); + } + + function testStandardUsage() { + + generate_mock_once('HTMLPurifier_IDAccumulator'); + + $this->assertFalse($this->context->exists('IDAccumulator')); + + $accumulator = new HTMLPurifier_IDAccumulatorMock(); + $this->context->register('IDAccumulator', $accumulator); + $this->assertTrue($this->context->exists('IDAccumulator')); + + $accumulator_2 =& $this->context->get('IDAccumulator'); + $this->assertReference($accumulator, $accumulator_2); + + $this->context->destroy('IDAccumulator'); + $this->assertFalse($this->context->exists('IDAccumulator')); + + $this->expectError('Attempted to retrieve non-existent variable IDAccumulator'); + $accumulator_3 =& $this->context->get('IDAccumulator'); + $this->assertNull($accumulator_3); + + $this->expectError('Attempted to destroy non-existent variable IDAccumulator'); + $this->context->destroy('IDAccumulator'); + + } + + function testReRegister() { + + $var = true; + $this->context->register('OnceOnly', $var); + + $this->expectError('Name OnceOnly produces collision, cannot re-register'); + $this->context->register('OnceOnly', $var); + + // destroy it, now registration is okay + $this->context->destroy('OnceOnly'); + $this->context->register('OnceOnly', $var); + + } + + function test_loadArray() { + + // references can be *really* wonky! + + $context_manual = new HTMLPurifier_Context(); + $context_load = new HTMLPurifier_Context(); + + $var1 = 1; + $var2 = 2; + + $context_manual->register('var1', $var1); + $context_manual->register('var2', $var2); + + // you MUST set up the references when constructing the array, + // otherwise the registered version will be a copy + $array = array( + 'var1' => &$var1, + 'var2' => &$var2 + ); + + $context_load->loadArray($array); + $this->assertIdentical($context_manual, $context_load); + + $var1 = 10; + $var2 = 20; + + $this->assertIdentical($context_manual, $context_load); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/CleanupTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/CleanupTest.php new file mode 100644 index 000000000..afcb0e39f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/CleanupTest.php @@ -0,0 +1,56 @@ +<?php + +generate_mock_once('HTMLPurifier_DefinitionCache'); + +class HTMLPurifier_DefinitionCache_Decorator_CleanupTest extends HTMLPurifier_DefinitionCache_DecoratorHarness +{ + + function setup() { + $this->cache = new HTMLPurifier_DefinitionCache_Decorator_Cleanup(); + parent::setup(); + } + + function setupMockForSuccess($op) { + $this->mock->expectOnce($op, array($this->def, $this->config)); + $this->mock->setReturnValue($op, true, array($this->def, $this->config)); + $this->mock->expectNever('cleanup'); + } + + function setupMockForFailure($op) { + $this->mock->expectOnce($op, array($this->def, $this->config)); + $this->mock->setReturnValue($op, false, array($this->def, $this->config)); + $this->mock->expectOnce('cleanup', array($this->config)); + } + + function test_get() { + $this->mock->expectOnce('get', array($this->config)); + $this->mock->setReturnValue('get', true, array($this->config)); + $this->mock->expectNever('cleanup'); + $this->assertEqual($this->cache->get($this->config), $this->def); + } + + function test_get_failure() { + $this->mock->expectOnce('get', array($this->config)); + $this->mock->setReturnValue('get', false, array($this->config)); + $this->mock->expectOnce('cleanup', array($this->config)); + $this->assertEqual($this->cache->get($this->config), false); + } + + function test_set() { + $this->setupMockForSuccess('set'); + $this->assertEqual($this->cache->set($this->def, $this->config), true); + } + + function test_replace() { + $this->setupMockForSuccess('replace'); + $this->assertEqual($this->cache->replace($this->def, $this->config), true); + } + + function test_add() { + $this->setupMockForSuccess('add'); + $this->assertEqual($this->cache->add($this->def, $this->config), true); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/MemoryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/MemoryTest.php new file mode 100644 index 000000000..55f2f249c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/Decorator/MemoryTest.php @@ -0,0 +1,70 @@ +<?php + +generate_mock_once('HTMLPurifier_DefinitionCache'); + +class HTMLPurifier_DefinitionCache_Decorator_MemoryTest extends HTMLPurifier_DefinitionCache_DecoratorHarness +{ + + function setup() { + $this->cache = new HTMLPurifier_DefinitionCache_Decorator_Memory(); + parent::setup(); + } + + function setupMockForSuccess($op) { + $this->mock->expectOnce($op, array($this->def, $this->config)); + $this->mock->setReturnValue($op, true, array($this->def, $this->config)); + $this->mock->expectNever('get'); + } + + function setupMockForFailure($op) { + $this->mock->expectOnce($op, array($this->def, $this->config)); + $this->mock->setReturnValue($op, false, array($this->def, $this->config)); + $this->mock->expectOnce('get', array($this->config)); + } + + function test_get() { + $this->mock->expectOnce('get', array($this->config)); // only ONE call! + $this->mock->setReturnValue('get', $this->def, array($this->config)); + $this->assertEqual($this->cache->get($this->config), $this->def); + $this->assertEqual($this->cache->get($this->config), $this->def); + } + + function test_set() { + $this->setupMockForSuccess('set', 'get'); + $this->assertEqual($this->cache->set($this->def, $this->config), true); + $this->assertEqual($this->cache->get($this->config), $this->def); + } + + function test_set_failure() { + $this->setupMockForFailure('set', 'get'); + $this->assertEqual($this->cache->set($this->def, $this->config), false); + $this->cache->get($this->config); + } + + function test_replace() { + $this->setupMockForSuccess('replace', 'get'); + $this->assertEqual($this->cache->replace($this->def, $this->config), true); + $this->assertEqual($this->cache->get($this->config), $this->def); + } + + function test_replace_failure() { + $this->setupMockForFailure('replace', 'get'); + $this->assertEqual($this->cache->replace($this->def, $this->config), false); + $this->cache->get($this->config); + } + + function test_add() { + $this->setupMockForSuccess('add', 'get'); + $this->assertEqual($this->cache->add($this->def, $this->config), true); + $this->assertEqual($this->cache->get($this->config), $this->def); + } + + function test_add_failure() { + $this->setupMockForFailure('add', 'get'); + $this->assertEqual($this->cache->add($this->def, $this->config), false); + $this->cache->get($this->config); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorHarness.php new file mode 100644 index 000000000..64951a3ed --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorHarness.php @@ -0,0 +1,23 @@ +<?php + +generate_mock_once('HTMLPurifier_DefinitionCache'); + +class HTMLPurifier_DefinitionCache_DecoratorHarness extends HTMLPurifier_DefinitionCacheHarness +{ + + function setup() { + $this->mock = new HTMLPurifier_DefinitionCacheMock(); + $this->mock->type = 'Test'; + $this->cache = $this->cache->decorate($this->mock); + $this->def = $this->generateDefinition(); + $this->config = $this->generateConfigMock(); + } + + function teardown() { + unset($this->mock); + unset($this->cache); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorTest.php new file mode 100644 index 000000000..f64967970 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/DecoratorTest.php @@ -0,0 +1,42 @@ +<?php + +class HTMLPurifier_DefinitionCache_DecoratorTest extends HTMLPurifier_DefinitionCacheHarness +{ + + function test() { + + generate_mock_once('HTMLPurifier_DefinitionCache'); + $mock = new HTMLPurifier_DefinitionCacheMock(); + $mock->type = 'Test'; + + $cache = new HTMLPurifier_DefinitionCache_Decorator(); + $cache = $cache->decorate($mock); + + $this->assertIdentical($cache->type, $mock->type); + + $def = $this->generateDefinition(); + $config = $this->generateConfigMock(); + + $mock->expectOnce('add', array($def, $config)); + $cache->add($def, $config); + + $mock->expectOnce('set', array($def, $config)); + $cache->set($def, $config); + + $mock->expectOnce('replace', array($def, $config)); + $cache->replace($def, $config); + + $mock->expectOnce('get', array($config)); + $cache->get($config); + + $mock->expectOnce('flush', array($config)); + $cache->flush($config); + + $mock->expectOnce('cleanup', array($config)); + $cache->cleanup($config); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest.php new file mode 100644 index 000000000..f6fec51ef --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest.php @@ -0,0 +1,225 @@ +<?php + +class HTMLPurifier_DefinitionCache_SerializerTest extends HTMLPurifier_DefinitionCacheHarness +{ + + function test() { + // XXX SimpleTest does some really crazy stuff in the background + // to do equality checks. Unfortunately, this makes some + // versions of PHP segfault. So we need to define a better, + // homebrew notion of equality and use that instead. For now, + // the identical asserts are commented out. + + $cache = new HTMLPurifier_DefinitionCache_Serializer('Test'); + + $config = $this->generateConfigMock('serial'); + $config->setReturnValue('get', 2, array('Test.DefinitionRev')); + $config->version = '1.0.0'; + + $config_md5 = '1.0.0,serial,2'; + + $file = realpath( + $rel_file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer/Test/' . + $config_md5 . '.ser' + ); + if($file && file_exists($file)) unlink($file); // prevent previous failures from causing problems + + $this->assertIdentical($config_md5, $cache->generateKey($config)); + + $def_original = $this->generateDefinition(); + + $cache->add($def_original, $config); + $this->assertFileExist($rel_file); + + $file_generated = $cache->generateFilePath($config); + $this->assertIdentical(realpath($rel_file), realpath($file_generated)); + + $def_1 = $cache->get($config); + // $this->assertIdentical($def_original, $def_1); + + $def_original->info_random = 'changed'; + + $cache->set($def_original, $config); + $def_2 = $cache->get($config); + + // $this->assertIdentical($def_original, $def_2); + // $this->assertNotEqual ($def_original, $def_1); + + $def_original->info_random = 'did it change?'; + + $this->assertFalse($cache->add($def_original, $config)); + $def_3 = $cache->get($config); + + // $this->assertNotEqual ($def_original, $def_3); // did not change! + // $this->assertIdentical($def_3, $def_2); + + $cache->replace($def_original, $config); + $def_4 = $cache->get($config); + // $this->assertIdentical($def_original, $def_4); + + $cache->remove($config); + $this->assertFileNotExist($file); + + $this->assertFalse($cache->replace($def_original, $config)); + $def_5 = $cache->get($config); + $this->assertFalse($def_5); + + } + + function test_errors() { + $cache = new HTMLPurifier_DefinitionCache_Serializer('Test'); + $def = $this->generateDefinition(); + $def->setup = true; + $def->type = 'NotTest'; + $config = $this->generateConfigMock('testfoo'); + + $this->expectError('Cannot use definition of type NotTest in cache for Test'); + $cache->add($def, $config); + + $this->expectError('Cannot use definition of type NotTest in cache for Test'); + $cache->set($def, $config); + + $this->expectError('Cannot use definition of type NotTest in cache for Test'); + $cache->replace($def, $config); + } + + function test_flush() { + + $cache = new HTMLPurifier_DefinitionCache_Serializer('Test'); + + $config1 = $this->generateConfigMock('test1'); + $config2 = $this->generateConfigMock('test2'); + $config3 = $this->generateConfigMock('test3'); + + $def1 = $this->generateDefinition(array('info_candles' => 1)); + $def2 = $this->generateDefinition(array('info_candles' => 2)); + $def3 = $this->generateDefinition(array('info_candles' => 3)); + + $cache->add($def1, $config1); + $cache->add($def2, $config2); + $cache->add($def3, $config3); + + $this->assertEqual($def1, $cache->get($config1)); + $this->assertEqual($def2, $cache->get($config2)); + $this->assertEqual($def3, $cache->get($config3)); + + $cache->flush($config1); // only essential directive is %Cache.SerializerPath + + $this->assertFalse($cache->get($config1)); + $this->assertFalse($cache->get($config2)); + $this->assertFalse($cache->get($config3)); + + } + + function testCleanup() { + + $cache = new HTMLPurifier_DefinitionCache_Serializer('Test'); + + // in order of age, oldest first + // note that configurations are all identical, but version/revision + // are different + + $config1 = $this->generateConfigMock(); + $config1->version = '0.9.0'; + $config1->setReturnValue('get', 574, array('Test.DefinitionRev')); + $def1 = $this->generateDefinition(array('info' => 1)); + + $config2 = $this->generateConfigMock(); + $config2->version = '1.0.0beta'; + $config2->setReturnValue('get', 1, array('Test.DefinitionRev')); + $def2 = $this->generateDefinition(array('info' => 3)); + + $cache->set($def1, $config1); + $cache->cleanup($config1); + $this->assertEqual($def1, $cache->get($config1)); // no change + + $cache->cleanup($config2); + $this->assertFalse($cache->get($config1)); + $this->assertFalse($cache->get($config2)); + + } + + function testCleanupOnlySameID() { + + $cache = new HTMLPurifier_DefinitionCache_Serializer('Test'); + + $config1 = $this->generateConfigMock('serial1'); + $config1->version = '1.0.0'; + $config1->setReturnValue('get', 1, array('Test.DefinitionRev')); + $def1 = $this->generateDefinition(array('info' => 1)); + + $config2 = $this->generateConfigMock('serial2'); + $config2->version = '1.0.0'; + $config2->setReturnValue('get', 34, array('Test.DefinitionRev')); + $def2 = $this->generateDefinition(array('info' => 3)); + + $cache->set($def1, $config1); + $cache->cleanup($config1); + $this->assertEqual($def1, $cache->get($config1)); // no change + + $cache->set($def2, $config2); + $cache->cleanup($config2); + $this->assertEqual($def1, $cache->get($config1)); + $this->assertEqual($def2, $cache->get($config2)); + + $cache->flush($config1); + } + + /** + * Asserts that a file exists, ignoring the stat cache + */ + function assertFileExist($file) { + clearstatcache(); + $this->assertTrue(file_exists($file), 'Expected ' . $file . ' exists'); + } + + /** + * Asserts that a file does not exist, ignoring the stat cache + */ + function assertFileNotExist($file) { + clearstatcache(); + $this->assertFalse(file_exists($file), 'Expected ' . $file . ' does not exist'); + } + + function testAlternatePath() { + + $cache = new HTMLPurifier_DefinitionCache_Serializer('Test'); + $config = $this->generateConfigMock('serial'); + $config->version = '1.0.0'; + $config->setReturnValue('get', 1, array('Test.DefinitionRev')); + $dir = dirname(__FILE__) . '/SerializerTest'; + $config->setReturnValue('get', $dir, array('Cache.SerializerPath')); + + $def_original = $this->generateDefinition(); + $cache->add($def_original, $config); + $this->assertFileExist($dir . '/Test/1.0.0,serial,1.ser'); + + unlink($dir . '/Test/1.0.0,serial,1.ser'); + rmdir( $dir . '/Test'); + + } + + function testAlternatePermissions() { + + $cache = new HTMLPurifier_DefinitionCache_Serializer('Test'); + $config = $this->generateConfigMock('serial'); + $config->version = '1.0.0'; + $config->setReturnValue('get', 1, array('Test.DefinitionRev')); + $dir = dirname(__FILE__) . '/SerializerTest'; + $config->setReturnValue('get', $dir, array('Cache.SerializerPath')); + $config->setReturnValue('get', 0777, array('Cache.SerializerPermissions')); + + $def_original = $this->generateDefinition(); + $cache->add($def_original, $config); + $this->assertFileExist($dir . '/Test/1.0.0,serial,1.ser'); + + $this->assertEqual(0666, 0777 & fileperms($dir . '/Test/1.0.0,serial,1.ser')); + $this->assertEqual(0777, 0777 & fileperms($dir . '/Test')); + + unlink($dir . '/Test/1.0.0,serial,1.ser'); + rmdir( $dir . '/Test'); + + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest/README b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest/README new file mode 100644 index 000000000..2e35c1c3d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCache/SerializerTest/README @@ -0,0 +1,3 @@ +This is a dummy file to prevent Git from ignoring this empty directory. + + vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheFactoryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheFactoryTest.php new file mode 100644 index 000000000..d50ef8be1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheFactoryTest.php @@ -0,0 +1,70 @@ +<?php + +class HTMLPurifier_DefinitionCacheFactoryTest extends HTMLPurifier_Harness +{ + + protected $factory; + protected $oldFactory; + + public function setUp() { + parent::setup(); + $this->factory = new HTMLPurifier_DefinitionCacheFactory(); + $this->oldFactory = HTMLPurifier_DefinitionCacheFactory::instance(); + HTMLPurifier_DefinitionCacheFactory::instance($this->factory); + } + + public function tearDown() { + HTMLPurifier_DefinitionCacheFactory::instance($this->oldFactory); + } + + function test_create() { + $cache = $this->factory->create('Test', $this->config); + $this->assertEqual($cache, new HTMLPurifier_DefinitionCache_Serializer('Test')); + } + + function test_create_withDecorator() { + $this->factory->addDecorator('Memory'); + $cache = $this->factory->create('Test', $this->config); + $cache_real = new HTMLPurifier_DefinitionCache_Decorator_Memory(); + $cache_real = $cache_real->decorate(new HTMLPurifier_DefinitionCache_Serializer('Test')); + $this->assertEqual($cache, $cache_real); + } + + function test_create_withDecoratorObject() { + $this->factory->addDecorator(new HTMLPurifier_DefinitionCache_Decorator_Memory()); + $cache = $this->factory->create('Test', $this->config); + $cache_real = new HTMLPurifier_DefinitionCache_Decorator_Memory(); + $cache_real = $cache_real->decorate(new HTMLPurifier_DefinitionCache_Serializer('Test')); + $this->assertEqual($cache, $cache_real); + } + + function test_create_recycling() { + $cache = $this->factory->create('Test', $this->config); + $cache2 = $this->factory->create('Test', $this->config); + $this->assertReference($cache, $cache2); + } + + function test_create_invalid() { + $this->config->set('Cache.DefinitionImpl', 'Invalid'); + $this->expectError('Unrecognized DefinitionCache Invalid, using Serializer instead'); + $cache = $this->factory->create('Test', $this->config); + $this->assertIsA($cache, 'HTMLPurifier_DefinitionCache_Serializer'); + } + + function test_null() { + $this->config->set('Cache.DefinitionImpl', null); + $cache = $this->factory->create('Test', $this->config); + $this->assertEqual($cache, new HTMLPurifier_DefinitionCache_Null('Test')); + } + + function test_register() { + generate_mock_once('HTMLPurifier_DefinitionCache'); + $this->config->set('Cache.DefinitionImpl', 'TestCache'); + $this->factory->register('TestCache', $class = 'HTMLPurifier_DefinitionCacheMock'); + $cache = $this->factory->create('Test', $this->config); + $this->assertIsA($cache, $class); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheHarness.php new file mode 100644 index 000000000..ab3cef451 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheHarness.php @@ -0,0 +1,34 @@ +<?php + +class HTMLPurifier_DefinitionCacheHarness extends HTMLPurifier_Harness +{ + + /** + * Generate a configuration mock object that returns $values + * to a getBatch() call + * @param $values Values to return when getBatch is invoked + */ + protected function generateConfigMock($serial = 'defaultserial') { + generate_mock_once('HTMLPurifier_Config'); + $config = new HTMLPurifier_ConfigMock(); + $config->setReturnValue('getBatchSerial', $serial, array('Test')); + $config->version = '1.0.0'; + return $config; + } + + /** + * Returns an anonymous def that has been setup and named Test + */ + protected function generateDefinition($member_vars = array()) { + $def = new HTMLPurifier_DefinitionTestable(); + $def->setup = true; + $def->type = 'Test'; + foreach ($member_vars as $key => $val) { + $def->$key = $val; + } + return $def; + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheTest.php new file mode 100644 index 000000000..5ad2ff9cb --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionCacheTest.php @@ -0,0 +1,32 @@ +<?php + +class HTMLPurifier_DefinitionCacheTest extends HTMLPurifier_Harness +{ + + function test_isOld() { + // using null subclass because parent is abstract + $cache = new HTMLPurifier_DefinitionCache_Null('Test'); + + generate_mock_once('HTMLPurifier_Config'); + $config = new HTMLPurifier_ConfigMock(); + $config->version = '1.0.0'; // hopefully no conflicts + $config->setReturnValue('get', 10, array('Test.DefinitionRev')); + $config->setReturnValue('getBatchSerial', 'hash', array('Test')); + + $this->assertIdentical($cache->isOld('1.0.0,hash,10', $config), false); + $this->assertIdentical($cache->isOld('1.5.0,hash,1', $config), true); + + $this->assertIdentical($cache->isOld('0.9.0,hash,1', $config), true); + $this->assertIdentical($cache->isOld('1.0.0,hash,1', $config), true); + $this->assertIdentical($cache->isOld('1.0.0beta,hash,11', $config), true); + + $this->assertIdentical($cache->isOld('0.9.0,hash2,1', $config), true); + $this->assertIdentical($cache->isOld('1.0.0,hash2,1', $config), false); // if hash is different, don't touch! + $this->assertIdentical($cache->isOld('1.0.0beta,hash2,11', $config), true); + $this->assertIdentical($cache->isOld('1.0.0-dev,hash2,11', $config), true); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTest.php new file mode 100644 index 000000000..38676ad7a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTest.php @@ -0,0 +1,20 @@ +<?php + +class HTMLPurifier_DefinitionTest extends HTMLPurifier_Harness +{ + function test_setup() { + $def = new HTMLPurifier_DefinitionTestable(); + $config = HTMLPurifier_Config::createDefault(); + $def->expectOnce('doSetup', array($config)); + $def->setup($config); + } + function test_setup_redundant() { + $def = new HTMLPurifier_DefinitionTestable(); + $config = HTMLPurifier_Config::createDefault(); + $def->expectNever('doSetup'); + $def->setup = true; + $def->setup($config); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTestable.php b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTestable.php new file mode 100644 index 000000000..312c57bb4 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DefinitionTestable.php @@ -0,0 +1,8 @@ +<?php + +Mock::generatePartial( + 'HTMLPurifier_Definition', + 'HTMLPurifier_DefinitionTestable', + array('doSetup')); + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/DoctypeRegistryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/DoctypeRegistryTest.php new file mode 100644 index 000000000..189003ace --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/DoctypeRegistryTest.php @@ -0,0 +1,77 @@ +<?php + +class HTMLPurifier_DoctypeRegistryTest extends HTMLPurifier_Harness +{ + + function test_register() { + + $registry = new HTMLPurifier_DoctypeRegistry(); + + $d = $registry->register( + $name = 'XHTML 1.0 Transitional', + $xml = true, + $modules = array('module-one', 'module-two'), + $tidyModules = array('lenient-module'), + $aliases = array('X10T') + ); + + $d2 = new HTMLPurifier_Doctype($name, $xml, $modules, $tidyModules, $aliases); + + $this->assertIdentical($d, $d2); + $this->assertSame($d, $registry->get('XHTML 1.0 Transitional')); + + // test shorthand + $d = $registry->register( + $name = 'XHTML 1.0 Strict', true, 'module', 'Tidy', 'X10S' + ); + $d2 = new HTMLPurifier_Doctype($name, true, array('module'), array('Tidy'), array('X10S')); + + $this->assertIdentical($d, $d2); + + } + + function test_get() { + + // see also alias and register tests + + $registry = new HTMLPurifier_DoctypeRegistry(); + + $this->expectError('Doctype XHTML 2.0 does not exist'); + $registry->get('XHTML 2.0'); + + // prevent XSS + $this->expectError('Doctype <foo> does not exist'); + $registry->get('<foo>'); + + } + + function testAliases() { + + $registry = new HTMLPurifier_DoctypeRegistry(); + + $d1 = $registry->register('Doc1', true, array(), array(), array('1')); + + $this->assertSame($d1, $registry->get('Doc1')); + $this->assertSame($d1, $registry->get('1')); + + $d2 = $registry->register('Doc2', true, array(), array(), array('2')); + + $this->assertSame($d2, $registry->get('Doc2')); + $this->assertSame($d2, $registry->get('2')); + + $d3 = $registry->register('1', true, array(), array(), array()); + + // literal name overrides alias + $this->assertSame($d3, $registry->get('1')); + + $d4 = $registry->register('One', true, array(), array(), array('1')); + + $this->assertSame($d4, $registry->get('One')); + // still it overrides + $this->assertSame($d3, $registry->get('1')); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ElementDefTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ElementDefTest.php new file mode 100644 index 000000000..500312b3c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ElementDefTest.php @@ -0,0 +1,91 @@ +<?php + +class HTMLPurifier_ElementDefTest extends HTMLPurifier_Harness +{ + + function test_mergeIn() { + + $def1 = new HTMLPurifier_ElementDef(); + $def2 = new HTMLPurifier_ElementDef(); + $def3 = new HTMLPurifier_ElementDef(); + + $old = 1; + $new = 2; + $overloaded_old = 3; + $overloaded_new = 4; + $removed = 5; + + $def1->standalone = true; + $def1->attr = array( + 0 => array('old-include'), + 'old-attr' => $old, + 'overloaded-attr' => $overloaded_old, + 'removed-attr' => $removed, + ); + $def1->attr_transform_pre = + $def1->attr_transform_post = array( + 'old-transform' => $old, + 'overloaded-transform' => $overloaded_old, + 'removed-transform' => $removed, + ); + $def1->child = $overloaded_old; + $def1->content_model = 'old'; + $def1->content_model_type = $overloaded_old; + $def1->descendants_are_inline = false; + $def1->excludes = array( + 'old' => true, + 'removed-old' => true + ); + + $def2->standalone = false; + $def2->attr = array( + 0 => array('new-include'), + 'new-attr' => $new, + 'overloaded-attr' => $overloaded_new, + 'removed-attr' => false, + ); + $def2->attr_transform_pre = + $def2->attr_transform_post = array( + 'new-transform' => $new, + 'overloaded-transform' => $overloaded_new, + 'removed-transform' => false, + ); + $def2->child = $new; + $def2->content_model = '#SUPER | new'; + $def2->content_model_type = $overloaded_new; + $def2->descendants_are_inline = true; + $def2->excludes = array( + 'new' => true, + 'removed-old' => false + ); + + $def1->mergeIn($def2); + $def1->mergeIn($def3); // empty, has no effect + + $this->assertIdentical($def1->standalone, true); + $this->assertIdentical($def1->attr, array( + 0 => array('old-include', 'new-include'), + 'old-attr' => $old, + 'overloaded-attr' => $overloaded_new, + 'new-attr' => $new, + )); + $this->assertIdentical($def1->attr_transform_pre, $def1->attr_transform_post); + $this->assertIdentical($def1->attr_transform_pre, array( + 'old-transform' => $old, + 'overloaded-transform' => $overloaded_new, + 'new-transform' => $new, + )); + $this->assertIdentical($def1->child, $new); + $this->assertIdentical($def1->content_model, 'old | new'); + $this->assertIdentical($def1->content_model_type, $overloaded_new); + $this->assertIdentical($def1->descendants_are_inline, true); + $this->assertIdentical($def1->excludes, array( + 'old' => true, + 'new' => true + )); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/EncoderTest.php b/lib/htmlpurifier/tests/HTMLPurifier/EncoderTest.php new file mode 100644 index 000000000..6084c39f3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/EncoderTest.php @@ -0,0 +1,215 @@ +<?php + +class HTMLPurifier_EncoderTest extends HTMLPurifier_Harness +{ + + protected $_entity_lookup; + + function setUp() { + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + parent::setUp(); + } + + function assertCleanUTF8($string, $expect = null) { + if ($expect === null) $expect = $string; + $this->assertIdentical(HTMLPurifier_Encoder::cleanUTF8($string), $expect, 'iconv: %s'); + $this->assertIdentical(HTMLPurifier_Encoder::cleanUTF8($string, true), $expect, 'PHP: %s'); + } + + function test_cleanUTF8() { + $this->assertCleanUTF8('Normal string.'); + $this->assertCleanUTF8("Test\tAllowed\nControl\rCharacters"); + $this->assertCleanUTF8("null byte: \0", 'null byte: '); + $this->assertCleanUTF8("\1\2\3\4\5\6\7", ''); + $this->assertCleanUTF8("\x7F", ''); // one byte invalid SGML char + $this->assertCleanUTF8("\xC2\x80", ''); // two byte invalid SGML + $this->assertCleanUTF8("\xF3\xBF\xBF\xBF"); // valid four byte + $this->assertCleanUTF8("\xDF\xFF", ''); // malformed UTF8 + // invalid codepoints + $this->assertCleanUTF8("\xED\xB0\x80", ''); + } + + function test_convertToUTF8_noConvert() { + // UTF-8 means that we don't touch it + $this->assertIdentical( + HTMLPurifier_Encoder::convertToUTF8("\xF6", $this->config, $this->context), + "\xF6", // this is invalid + 'Expected identical [Binary: F6]' + ); + } + + function test_convertToUTF8_spuriousEncoding() { + $this->config->set('Core.Encoding', 'utf99'); + $this->expectError('Invalid encoding utf99'); + $this->assertIdentical( + HTMLPurifier_Encoder::convertToUTF8("\xF6", $this->config, $this->context), + '' + ); + } + + function test_convertToUTF8_iso8859_1() { + $this->config->set('Core.Encoding', 'ISO-8859-1'); + $this->assertIdentical( + HTMLPurifier_Encoder::convertToUTF8("\xF6", $this->config, $this->context), + "\xC3\xB6" + ); + } + + function test_convertToUTF8_withoutIconv() { + $this->config->set('Core.Encoding', 'ISO-8859-1'); + $this->config->set('Test.ForceNoIconv', true); + $this->assertIdentical( + HTMLPurifier_Encoder::convertToUTF8("\xF6", $this->config, $this->context), + "\xC3\xB6" + ); + + } + + function getZhongWen() { + return "\xE4\xB8\xAD\xE6\x96\x87 (Chinese)"; + } + + function test_convertFromUTF8_utf8() { + // UTF-8 means that we don't touch it + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $this->config, $this->context), + "\xC3\xB6" + ); + } + + function test_convertFromUTF8_iso8859_1() { + $this->config->set('Core.Encoding', 'ISO-8859-1'); + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $this->config, $this->context), + "\xF6", + 'Expected identical [Binary: F6]' + ); + } + + function test_convertFromUTF8_iconvNoChars() { + if (!function_exists('iconv')) return; + $this->config->set('Core.Encoding', 'ISO-8859-1'); + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8($this->getZhongWen(), $this->config, $this->context), + " (Chinese)" + ); + } + + function test_convertFromUTF8_phpNormal() { + // Plain PHP implementation has slightly different behavior + $this->config->set('Core.Encoding', 'ISO-8859-1'); + $this->config->set('Test.ForceNoIconv', true); + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8("\xC3\xB6", $this->config, $this->context), + "\xF6", + 'Expected identical [Binary: F6]' + ); + } + + function test_convertFromUTF8_phpNoChars() { + $this->config->set('Core.Encoding', 'ISO-8859-1'); + $this->config->set('Test.ForceNoIconv', true); + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8($this->getZhongWen(), $this->config, $this->context), + "?? (Chinese)" + ); + } + + function test_convertFromUTF8_withProtection() { + // Preserve the characters! + $this->config->set('Core.Encoding', 'ISO-8859-1'); + $this->config->set('Core.EscapeNonASCIICharacters', true); + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8($this->getZhongWen(), $this->config, $this->context), + "中文 (Chinese)" + ); + } + + function test_convertFromUTF8_withProtectionButUtf8() { + // Preserve the characters! + $this->config->set('Core.EscapeNonASCIICharacters', true); + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8($this->getZhongWen(), $this->config, $this->context), + "中文 (Chinese)" + ); + } + + function test_convertToASCIIDumbLossless() { + + // Uppercase thorn letter + $this->assertIdentical( + HTMLPurifier_Encoder::convertToASCIIDumbLossless("\xC3\x9Eorn"), + "Þorn" + ); + + $this->assertIdentical( + HTMLPurifier_Encoder::convertToASCIIDumbLossless("an"), + "an" + ); + + // test up to four bytes + $this->assertIdentical( + HTMLPurifier_Encoder::convertToASCIIDumbLossless("\xF3\xA0\x80\xA0"), + "󠀠" + ); + + } + + function assertASCIISupportCheck($enc, $ret) { + $test = HTMLPurifier_Encoder::testEncodingSupportsASCII($enc, true); + if ($test === false) return; + $this->assertIdentical( + HTMLPurifier_Encoder::testEncodingSupportsASCII($enc), + $ret + ); + $this->assertIdentical( + HTMLPurifier_Encoder::testEncodingSupportsASCII($enc, true), + $ret + ); + } + + function test_testEncodingSupportsASCII() { + $this->assertASCIISupportCheck('Shift_JIS', array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~')); + $this->assertASCIISupportCheck('JOHAB', array("\xE2\x82\xA9" => '\\')); + $this->assertASCIISupportCheck('ISO-8859-1', array()); + $this->assertASCIISupportCheck('dontexist', array()); // canary + } + + function testShiftJIS() { + if (!function_exists('iconv')) return; + $this->config->set('Core.Encoding', 'Shift_JIS'); + // This actually looks like a Yen, but we're going to treat it differently + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8('\\~', $this->config, $this->context), + '\\~' + ); + $this->assertIdentical( + HTMLPurifier_Encoder::convertToUTF8('\\~', $this->config, $this->context), + '\\~' + ); + } + + function testIconvTruncateBug() { + if (!function_exists('iconv')) return; + if (HTMLPurifier_Encoder::testIconvTruncateBug() !== HTMLPurifier_Encoder::ICONV_TRUNCATES) return; + $this->config->set('Core.Encoding', 'ISO-8859-1'); + $this->assertIdentical( + HTMLPurifier_Encoder::convertFromUTF8("\xE4\xB8\xAD" . str_repeat('a', 10000), $this->config, $this->context), + str_repeat('a', 10000) + ); + } + + function testIconvChunking() { + if (!function_exists('iconv')) return; + if (HTMLPurifier_Encoder::testIconvTruncateBug() !== HTMLPurifier_Encoder::ICONV_TRUNCATES) return; + $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "a\xF3\xA0\x80\xA0b", 4), 'ab'); + $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aa\xE4\xB8\xADb", 4), 'aab'); + $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aaa\xCE\xB1b", 4), 'aaab'); + $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aaaa\xF3\xA0\x80\xA0b", 4), 'aaaab'); + $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aaaa\xE4\xB8\xADb", 4), 'aaaab'); + $this->assertIdentical(HTMLPurifier_Encoder::iconv('utf-8', 'iso-8859-1//IGNORE', "aaaa\xCE\xB1b", 4), 'aaaab'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/EntityLookupTest.php b/lib/htmlpurifier/tests/HTMLPurifier/EntityLookupTest.php new file mode 100644 index 000000000..e715f64a2 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/EntityLookupTest.php @@ -0,0 +1,27 @@ +<?php + +// this page is UTF-8 encoded! + +class HTMLPurifier_EntityLookupTest extends HTMLPurifier_Harness +{ + + function test() { + + $lookup = HTMLPurifier_EntityLookup::instance(); + + // latin char + $this->assertIdentical('â', $lookup->table['acirc']); + + // special char + $this->assertIdentical('"', $lookup->table['quot']); + $this->assertIdentical('“', $lookup->table['ldquo']); + $this->assertIdentical('<', $lookup->table['lt']); // expressed strangely in source file + + // symbol char + $this->assertIdentical('θ', $lookup->table['theta']); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/EntityParserTest.php b/lib/htmlpurifier/tests/HTMLPurifier/EntityParserTest.php new file mode 100644 index 000000000..17ef62a18 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/EntityParserTest.php @@ -0,0 +1,85 @@ +<?php + +class HTMLPurifier_EntityParserTest extends HTMLPurifier_Harness +{ + + protected $EntityParser; + + public function setUp() { + $this->EntityParser = new HTMLPurifier_EntityParser(); + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + } + + function test_substituteNonSpecialEntities() { + $char_theta = $this->_entity_lookup->table['theta']; + $this->assertIdentical($char_theta, + $this->EntityParser->substituteNonSpecialEntities('θ') ); + $this->assertIdentical('"', + $this->EntityParser->substituteNonSpecialEntities('"') ); + + // numeric tests, adapted from Feyd + $args = array(); + $args[] = array(1114112,false ); + $args[] = array(1114111,'F48FBFBF'); // 0x0010FFFF + $args[] = array(1048576,'F4808080'); // 0x00100000 + $args[] = array(1048575,'F3BFBFBF'); // 0x000FFFFF + $args[] = array(262144, 'F1808080'); // 0x00040000 + $args[] = array(262143, 'F0BFBFBF'); // 0x0003FFFF + $args[] = array(65536, 'F0908080'); // 0x00010000 + $args[] = array(65535, 'EFBFBF' ); // 0x0000FFFF + $args[] = array(57344, 'EE8080' ); // 0x0000E000 + $args[] = array(57343, false ); // 0x0000DFFF these are ill-formed + $args[] = array(56040, false ); // 0x0000DAE8 these are ill-formed + $args[] = array(55296, false ); // 0x0000D800 these are ill-formed + $args[] = array(55295, 'ED9FBF' ); // 0x0000D7FF + $args[] = array(53248, 'ED8080' ); // 0x0000D000 + $args[] = array(53247, 'ECBFBF' ); // 0x0000CFFF + $args[] = array(4096, 'E18080' ); // 0x00001000 + $args[] = array(4095, 'E0BFBF' ); // 0x00000FFF + $args[] = array(2048, 'E0A080' ); // 0x00000800 + $args[] = array(2047, 'DFBF' ); // 0x000007FF + $args[] = array(128, 'C280' ); // 0x00000080 invalid SGML char + $args[] = array(127, '7F' ); // 0x0000007F invalid SGML char + $args[] = array(0, '00' ); // 0x00000000 invalid SGML char + + $args[] = array(20108, 'E4BA8C' ); // 0x00004E8C + $args[] = array(77, '4D' ); // 0x0000004D + $args[] = array(66306, 'F0908C82'); // 0x00010302 + $args[] = array(1072, 'D0B0' ); // 0x00000430 + + foreach ($args as $arg) { + $string = '&#' . $arg[0] . ';' . // decimal + '&#x' . dechex($arg[0]) . ';'; // hex + $expect = ''; + if ($arg[1] !== false) { + // this is only for PHP 5, the below is PHP 5 and PHP 4 + //$chars = str_split($arg[1], 2); + $chars = array(); + // strlen must be called in loop because strings size changes + for ($i = 0; strlen($arg[1]) > $i; $i += 2) { + $chars[] = $arg[1][$i] . $arg[1][$i+1]; + } + foreach ($chars as $char) { + $expect .= chr(hexdec($char)); + } + $expect .= $expect; // double it + } + $this->assertIdentical( + $this->EntityParser->substituteNonSpecialEntities($string), + $expect, + 'Identical expectation [Hex: '. dechex($arg[0]) .']' + ); + } + + } + + function test_substituteSpecialEntities() { + $this->assertIdentical( + "'", + $this->EntityParser->substituteSpecialEntities(''') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorEMock.php b/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorEMock.php new file mode 100644 index 000000000..bbd096569 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorEMock.php @@ -0,0 +1,48 @@ +<?php + +generate_mock_once('HTMLPurifier_ErrorCollector'); + +/** + * Extended error collector mock that has the ability to expect context + */ +class HTMLPurifier_ErrorCollectorEMock extends HTMLPurifier_ErrorCollectorMock +{ + + private $_context; + private $_expected_context = array(); + private $_expected_context_at = array(); + + public function prepare($context) { + $this->_context = $context; + } + + public function expectContext($key, $value) { + $this->_expected_context[$key] = $value; + } + public function expectContextAt($step, $key, $value) { + $this->_expected_context_at[$step][$key] = $value; + } + + public function send($v1, $v2) { + // test for context + $context = SimpleTest::getContext(); + $test = $context->getTest(); + $mock = $this->mock; + + foreach ($this->_expected_context as $key => $value) { + $test->assertEqual($value, $this->_context->get($key)); + } + $step = $mock->getCallCount('send'); + if (isset($this->_expected_context_at[$step])) { + foreach ($this->_expected_context_at[$step] as $key => $value) { + $test->assertEqual($value, $this->_context->get($key)); + } + } + // boilerplate mock code, does not have return value or references + $args = func_get_args(); + $mock->invoke('send', $args); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorTest.php new file mode 100644 index 000000000..09becba53 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ErrorCollectorTest.php @@ -0,0 +1,157 @@ +<?php + +/** + * @warning HTML output is in flux, but eventually needs to be stabilized. + */ +class HTMLPurifier_ErrorCollectorTest extends HTMLPurifier_Harness +{ + + protected $language, $generator, $line; + protected $collector; + + public function setup() { + generate_mock_once('HTMLPurifier_Language'); + generate_mock_once('HTMLPurifier_Generator'); + parent::setup(); + $this->language = new HTMLPurifier_LanguageMock(); + $this->language->setReturnValue('getErrorName', 'Error', array(E_ERROR)); + $this->language->setReturnValue('getErrorName', 'Warning', array(E_WARNING)); + $this->language->setReturnValue('getErrorName', 'Notice', array(E_NOTICE)); + // this might prove to be troublesome if we need to set config + $this->generator = new HTMLPurifier_Generator($this->config, $this->context); + $this->line = false; + $this->context->register('Locale', $this->language); + $this->context->register('CurrentLine', $this->line); + $this->context->register('Generator', $this->generator); + $this->collector = new HTMLPurifier_ErrorCollector($this->context); + } + + function test() { + + $language = $this->language; + $language->setReturnValue('getMessage', 'Message 1', array('message-1')); + $language->setReturnValue('formatMessage', 'Message 2', array('message-2', array(1 => 'param'))); + $language->setReturnValue('formatMessage', ' at line 23', array('ErrorCollector: At line', array('line' => 23))); + $language->setReturnValue('formatMessage', ' at line 3', array('ErrorCollector: At line', array('line' => 3))); + + $this->line = 23; + $this->collector->send(E_ERROR, 'message-1'); + + $this->line = 3; + $this->collector->send(E_WARNING, 'message-2', 'param'); + + $result = array( + 0 => array(23, E_ERROR, 'Message 1', array()), + 1 => array(3, E_WARNING, 'Message 2', array()) + ); + + $this->assertIdentical($this->collector->getRaw(), $result); + + /* + $formatted_result = + '<ul><li><strong>Warning</strong>: Message 2 at line 3</li>'. + '<li><strong>Error</strong>: Message 1 at line 23</li></ul>'; + + $this->assertIdentical($this->collector->getHTMLFormatted($this->config), $formatted_result); + */ + + } + + function testNoErrors() { + $this->language->setReturnValue('getMessage', 'No errors', array('ErrorCollector: No errors')); + + $formatted_result = '<p>No errors</p>'; + $this->assertIdentical( + $this->collector->getHTMLFormatted($this->config), + $formatted_result + ); + } + + function testNoLineNumbers() { + $this->language->setReturnValue('getMessage', 'Message 1', array('message-1')); + $this->language->setReturnValue('getMessage', 'Message 2', array('message-2')); + + $this->collector->send(E_ERROR, 'message-1'); + $this->collector->send(E_ERROR, 'message-2'); + + $result = array( + 0 => array(false, E_ERROR, 'Message 1', array()), + 1 => array(false, E_ERROR, 'Message 2', array()) + ); + $this->assertIdentical($this->collector->getRaw(), $result); + + /* + $formatted_result = + '<ul><li><strong>Error</strong>: Message 1</li>'. + '<li><strong>Error</strong>: Message 2</li></ul>'; + $this->assertIdentical($this->collector->getHTMLFormatted($this->config), $formatted_result); + */ + } + + function testContextSubstitutions() { + + $current_token = false; + $this->context->register('CurrentToken', $current_token); + + // 0 + $current_token = new HTMLPurifier_Token_Start('a', array('href' => 'http://example.com'), 32); + $this->language->setReturnValue('formatMessage', 'Token message', + array('message-data-token', array('CurrentToken' => $current_token))); + $this->collector->send(E_NOTICE, 'message-data-token'); + + $current_attr = 'href'; + $this->language->setReturnValue('formatMessage', '$CurrentAttr.Name => $CurrentAttr.Value', + array('message-attr', array('CurrentToken' => $current_token))); + + // 1 + $this->collector->send(E_NOTICE, 'message-attr'); // test when context isn't available + + // 2 + $this->context->register('CurrentAttr', $current_attr); + $this->collector->send(E_NOTICE, 'message-attr'); + + $result = array( + 0 => array(32, E_NOTICE, 'Token message', array()), + 1 => array(32, E_NOTICE, '$CurrentAttr.Name => $CurrentAttr.Value', array()), + 2 => array(32, E_NOTICE, 'href => http://example.com', array()) + ); + $this->assertIdentical($this->collector->getRaw(), $result); + + } + + /* + function testNestedErrors() { + $this->language->setReturnValue('getMessage', 'Message 1', array('message-1')); + $this->language->setReturnValue('getMessage', 'Message 2', array('message-2')); + $this->language->setReturnValue('formatMessage', 'End Message', array('end-message', array(1 => 'param'))); + $this->language->setReturnValue('formatMessage', ' at line 4', array('ErrorCollector: At line', array('line' => 4))); + + $this->line = 4; + $this->collector->start(); + $this->collector->send(E_WARNING, 'message-1'); + $this->collector->send(E_NOTICE, 'message-2'); + $this->collector->end(E_NOTICE, 'end-message', 'param'); + + $expect = array( + 0 => array(4, E_NOTICE, 'End Message', array( + 0 => array(4, E_WARNING, 'Message 1', array()), + 1 => array(4, E_NOTICE, 'Message 2', array()), + )), + ); + $result = $this->collector->getRaw(); + $this->assertIdentical($result, $expect); + + $formatted_expect = + '<ul><li><strong>Notice</strong>: End Message at line 4<ul>'. + '<li><strong>Warning</strong>: Message 1 at line 4</li>'. + '<li><strong>Notice</strong>: Message 2 at line 4</li></ul>'. + '</li></ul>'; + $formatted_result = $this->collector->getHTMLFormatted($this->config); + $this->assertIdentical($formatted_result, $formatted_expect); + + } + */ + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/ErrorsHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/ErrorsHarness.php new file mode 100644 index 000000000..f7c673ca5 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/ErrorsHarness.php @@ -0,0 +1,38 @@ +<?php + +/** + * @todo Make the callCount variable actually work, so we can precisely + * specify what errors we want: no more, no less + */ +class HTMLPurifier_ErrorsHarness extends HTMLPurifier_Harness +{ + + protected $config, $context; + protected $collector, $generator, $callCount; + + public function setup() { + $this->config = HTMLPurifier_Config::create(array('Core.CollectErrors' => true)); + $this->context = new HTMLPurifier_Context(); + generate_mock_once('HTMLPurifier_ErrorCollector'); + $this->collector = new HTMLPurifier_ErrorCollectorEMock(); + $this->collector->prepare($this->context); + $this->context->register('ErrorCollector', $this->collector); + $this->callCount = 0; + } + + protected function expectNoErrorCollection() { + $this->collector->expectNever('send'); + } + + protected function expectErrorCollection() { + $args = func_get_args(); + $this->collector->expectOnce('send', $args); + } + + protected function expectContext($key, $value) { + $this->collector->expectContext($key, $value); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php new file mode 100644 index 000000000..3466d6aa2 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php @@ -0,0 +1,231 @@ +<?php + +/** + * @todo Assimilate CSSTidy into our library + */ +class HTMLPurifier_Filter_ExtractStyleBlocksTest extends HTMLPurifier_Harness +{ + + // usual use case: + function test_tokenizeHTML_extractStyleBlocks() { + $this->config->set('Filter.ExtractStyleBlocks', true); + $purifier = new HTMLPurifier($this->config); + $result = $purifier->purify('<style type="text/css">.foo {text-align:center;bogus:remove-me;} body.class[foo="attr"] {text-align:right;}</style>Test<style>* {font-size:12pt;}</style>'); + $this->assertIdentical($result, 'Test'); + $this->assertIdentical($purifier->context->get('StyleBlocks'), + array( + ".foo {\ntext-align:center;\n}", + "* {\nfont-size:12pt;\n}" + ) + ); + } + + function assertExtractStyleBlocks($html, $expect = true, $styles = array()) { + $filter = new HTMLPurifier_Filter_ExtractStyleBlocks(); // disable cleaning + if ($expect === true) $expect = $html; + $this->config->set('Filter.ExtractStyleBlocks.TidyImpl', false); + $result = $filter->preFilter($html, $this->config, $this->context); + $this->assertIdentical($result, $expect); + $this->assertIdentical($this->context->get('StyleBlocks'), $styles); + } + + function test_extractStyleBlocks_preserve() { + $this->assertExtractStyleBlocks('Foobar'); + } + + function test_extractStyleBlocks_allStyle() { + $this->assertExtractStyleBlocks('<style>foo</style>', '', array('foo')); + } + + function test_extractStyleBlocks_multipleBlocks() { + $this->assertExtractStyleBlocks( + "<style>1</style><style>2</style>NOP<style>4</style>", + "NOP", + array('1', '2', '4') + ); + } + + function test_extractStyleBlocks_blockWithAttributes() { + $this->assertExtractStyleBlocks( + '<style type="text/css">css</style>', + '', + array('css') + ); + } + + function test_extractStyleBlocks_styleWithPadding() { + $this->assertExtractStyleBlocks( + "Alas<styled>Awesome</styled>\n<style>foo</style> Trendy!", + "Alas<styled>Awesome</styled>\n Trendy!", + array('foo') + ); + } + + function assertCleanCSS($input, $expect = true) { + $filter = new HTMLPurifier_Filter_ExtractStyleBlocks(); + if ($expect === true) $expect = $input; + $this->normalize($input); + $this->normalize($expect); + $result = $filter->cleanCSS($input, $this->config, $this->context); + $this->assertIdentical($result, $expect); + } + + function test_cleanCSS_malformed() { + $this->assertCleanCSS('</style>', ''); + } + + function test_cleanCSS_selector() { + $this->assertCleanCSS("a .foo #id div.cl#foo {\nfont-weight:700;\n}"); + } + + function test_cleanCSS_angledBrackets() { + // [Content] No longer can smuggle in angled brackets using + // font-family; when we add support for 'content', reinstate + // this test. + //$this->assertCleanCSS( + // ".class {\nfont-family:'</style>';\n}", + // ".class {\nfont-family:\"\\3C /style\\3E \";\n}" + //); + } + + function test_cleanCSS_angledBrackets2() { + // CSSTidy's behavior in this case is wrong, and should be fixed + //$this->assertCleanCSS( + // "span[title=\"</style>\"] {\nfont-size:12pt;\n}", + // "span[title=\"\\3C /style\\3E \"] {\nfont-size:12pt;\n}" + //); + } + + function test_cleanCSS_bogus() { + $this->assertCleanCSS("div {bogus:tree;}", "div {\n}"); + } + + /* [CONTENT] + function test_cleanCSS_escapeCodes() { + $this->assertCleanCSS( + ".class {\nfont-family:\"\\3C /style\\3E \";\n}" + ); + } + + function test_cleanCSS_noEscapeCodes() { + $this->config->set('Filter.ExtractStyleBlocks.Escaping', false); + $this->assertCleanCSS( + ".class {\nfont-family:\"</style>\";\n}" + ); + } + */ + + function test_cleanCSS_scope() { + $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo'); + $this->assertCleanCSS( + "p {\ntext-indent:1em;\n}", + "#foo p {\ntext-indent:1em;\n}" + ); + } + + function test_cleanCSS_scopeWithSelectorCommas() { + $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo'); + $this->assertCleanCSS( + "b, i {\ntext-decoration:underline;\n}", + "#foo b, #foo i {\ntext-decoration:underline;\n}" + ); + } + + function test_cleanCSS_scopeWithNaughtySelector() { + $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo'); + $this->assertCleanCSS(" + p {\ntext-indent:1em;\n}", ''); + } + + function test_cleanCSS_scopeWithMultipleNaughtySelectors() { + $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo'); + $this->assertCleanCSS(" ++ ++ p {\ntext-indent:1em;\n}", ''); + } + + function test_cleanCSS_scopeWithCommas() { + $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo, .bar'); + $this->assertCleanCSS( + "p {\ntext-indent:1em;\n}", + "#foo p, .bar p {\ntext-indent:1em;\n}" + ); + } + + function test_cleanCSS_scopeAllWithCommas() { + $this->config->set('Filter.ExtractStyleBlocks.Scope', '#foo, .bar'); + $this->assertCleanCSS( + "p, div {\ntext-indent:1em;\n}", + "#foo p, .bar p, #foo div, .bar div {\ntext-indent:1em;\n}" + ); + } + + function test_cleanCSS_scopeWithConflicts() { + $this->config->set('Filter.ExtractStyleBlocks.Scope', 'p'); + $this->assertCleanCSS( +"div { +text-align:right; +} + +p div { +text-align:left; +}", + +"p div { +text-align:right; +} + +p p div { +text-align:left; +}" + ); + } + + function test_removeComments() { + $this->assertCleanCSS( +"<!-- +div { +text-align:right; +} +-->", +"div { +text-align:right; +}" + ); + } + + function test_atSelector() { + $this->assertCleanCSS( +"{ + b { text-align: center; } +}", +"" + ); + } + + function test_selectorValidation() { + $this->assertCleanCSS( +"&, & { +text-align: center; +}", +"" + ); + $this->assertCleanCSS( +"&, b { +text-align:center; +}", +"b { +text-align:center; +}" + ); + $this->assertCleanCSS( +"& a #foo:hover.bar +b > i { +text-align:center; +}", +"a #foo:hover.bar + b \\3E i { +text-align:center; +}" + ); + $this->assertCleanCSS("doesnt-exist { text-align:center }", ""); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/GeneratorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/GeneratorTest.php new file mode 100644 index 000000000..46b1dcf6b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/GeneratorTest.php @@ -0,0 +1,288 @@ +<?php + +class HTMLPurifier_GeneratorTest extends HTMLPurifier_Harness +{ + + /** + * Entity lookup table to help for a few tests. + */ + private $_entity_lookup; + + public function __construct() { + parent::__construct(); + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + } + + public function setUp() { + parent::setUp(); + $this->config->set('Output.Newline', "\n"); + } + + /** + * Creates a generator based on config and context member variables. + */ + protected function createGenerator() { + return new HTMLPurifier_Generator($this->config, $this->context); + } + + protected function assertGenerateFromToken($token, $html) { + $generator = $this->createGenerator(); + $result = $generator->generateFromToken($token); + $this->assertIdentical($result, $html); + } + + function test_generateFromToken_text() { + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Text('Foobar.<>'), + 'Foobar.<>' + ); + } + + function test_generateFromToken_startWithAttr() { + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Start('a', + array('href' => 'dyn?a=foo&b=bar') + ), + '<a href="dyn?a=foo&b=bar">' + ); + } + + function test_generateFromToken_end() { + $this->assertGenerateFromToken( + new HTMLPurifier_Token_End('b'), + '</b>' + ); + } + + function test_generateFromToken_emptyWithAttr() { + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Empty('br', + array('style' => 'font-family:"Courier New";') + ), + '<br style="font-family:"Courier New";" />' + ); + } + + function test_generateFromToken_startNoAttr() { + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Start('asdf'), + '<asdf>' + ); + } + + function test_generateFromToken_emptyNoAttr() { + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Empty('br'), + '<br />' + ); + } + + function test_generateFromToken_error() { + $this->expectError('Cannot generate HTML from non-HTMLPurifier_Token object'); + $this->assertGenerateFromToken( null, '' ); + } + + function test_generateFromToken_unicode() { + $theta_char = $this->_entity_lookup->table['theta']; + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Text($theta_char), + $theta_char + ); + } + + function test_generateFromToken_backtick() { + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Start('img', array('alt' => '`foo')), + '<img alt="`foo ">' + ); + } + + function test_generateFromToken_backtickDisabled() { + $this->config->set('Output.FixInnerHTML', false); + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Start('img', array('alt' => '`')), + '<img alt="`">' + ); + } + + function test_generateFromToken_backtickNoChange() { + $this->assertGenerateFromToken( + new HTMLPurifier_Token_Start('img', array('alt' => '`foo` bar')), + '<img alt="`foo` bar">' + ); + } + + function assertGenerateAttributes($attr, $expect, $element = false) { + $generator = $this->createGenerator(); + $result = $generator->generateAttributes($attr, $element); + $this->assertIdentical($result, $expect); + } + + function test_generateAttributes_blank() { + $this->assertGenerateAttributes(array(), ''); + } + + function test_generateAttributes_basic() { + $this->assertGenerateAttributes( + array('href' => 'dyn?a=foo&b=bar'), + 'href="dyn?a=foo&b=bar"' + ); + } + + function test_generateAttributes_doubleQuote() { + $this->assertGenerateAttributes( + array('style' => 'font-family:"Courier New";'), + 'style="font-family:"Courier New";"' + ); + } + + function test_generateAttributes_singleQuote() { + $this->assertGenerateAttributes( + array('style' => 'font-family:\'Courier New\';'), + 'style="font-family:\'Courier New\';"' + ); + } + + function test_generateAttributes_multiple() { + $this->assertGenerateAttributes( + array('src' => 'picture.jpg', 'alt' => 'Short & interesting'), + 'src="picture.jpg" alt="Short & interesting"' + ); + } + + function test_generateAttributes_specialChar() { + $theta_char = $this->_entity_lookup->table['theta']; + $this->assertGenerateAttributes( + array('title' => 'Theta is ' . $theta_char), + 'title="Theta is ' . $theta_char . '"' + ); + } + + + function test_generateAttributes_minimized() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Transitional'); + $this->assertGenerateAttributes( + array('compact' => 'compact'), 'compact', 'menu' + ); + } + + function test_generateFromTokens() { + + $this->assertGeneration( + array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('Foobar!'), + new HTMLPurifier_Token_End('b') + ), + '<b>Foobar!</b>' + ); + + } + + protected function assertGeneration($tokens, $expect) { + $generator = new HTMLPurifier_Generator($this->config, $this->context); + $result = $generator->generateFromTokens($tokens); + $this->assertIdentical($expect, $result); + } + + function test_generateFromTokens_Scripting() { + $this->assertGeneration( + array( + new HTMLPurifier_Token_Start('script'), + new HTMLPurifier_Token_Text('alert(3 < 5);'), + new HTMLPurifier_Token_End('script') + ), + "<script><!--//--><![CDATA[//><!--\nalert(3 < 5);\n//--><!]]></script>" + ); + } + + function test_generateFromTokens_Scripting_missingCloseTag() { + $this->assertGeneration( + array( + new HTMLPurifier_Token_Start('script'), + new HTMLPurifier_Token_Text('alert(3 < 5);'), + ), + "<script>alert(3 < 5);" + ); + } + + function test_generateFromTokens_Scripting_doubleBlock() { + $this->assertGeneration( + array( + new HTMLPurifier_Token_Start('script'), + new HTMLPurifier_Token_Text('alert(3 < 5);'), + new HTMLPurifier_Token_Text('foo();'), + new HTMLPurifier_Token_End('script') + ), + "<script>alert(3 < 5);foo();</script>" + ); + } + + function test_generateFromTokens_Scripting_disableWrapper() { + $this->config->set('Output.CommentScriptContents', false); + $this->assertGeneration( + array( + new HTMLPurifier_Token_Start('script'), + new HTMLPurifier_Token_Text('alert(3 < 5);'), + new HTMLPurifier_Token_End('script') + ), + "<script>alert(3 < 5);</script>" + ); + } + + function test_generateFromTokens_XHTMLoff() { + $this->config->set('HTML.XHTML', false); + + // omit trailing slash + $this->assertGeneration( + array( new HTMLPurifier_Token_Empty('br') ), + '<br>' + ); + + // there should be a test for attribute minimization, but it is + // impossible for something like that to happen due to our current + // definitions! fix it later + + // namespaced attributes must be dropped + $this->assertGeneration( + array( new HTMLPurifier_Token_Start('p', array('xml:lang'=>'fr')) ), + '<p>' + ); + + } + + function test_generateFromTokens_TidyFormat() { + // abort test if tidy isn't loaded + if (!extension_loaded('tidy')) return; + + // just don't test; Tidy is exploding on me. + return; + + $this->config->set('Core.TidyFormat', true); + $this->config->set('Output.Newline', "\n"); + + // nice wrapping please + $this->assertGeneration( + array( + new HTMLPurifier_Token_Start('div'), + new HTMLPurifier_Token_Text('Text'), + new HTMLPurifier_Token_End('div') + ), + "<div>\n Text\n</div>\n" + ); + + } + + function test_generateFromTokens_sortAttr() { + $this->config->set('Output.SortAttr', true); + + $this->assertGeneration( + array( new HTMLPurifier_Token_Start('p', array('b'=>'c', 'a'=>'d')) ), + '<p a="d" b="c">' + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLDefinitionTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLDefinitionTest.php new file mode 100644 index 000000000..6640cb0d0 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLDefinitionTest.php @@ -0,0 +1,355 @@ +<?php + +class HTMLPurifier_HTMLDefinitionTest extends HTMLPurifier_Harness +{ + + function expectError($error = false, $message = '%s') { + // Because we're testing a definition, it's vital that the cache + // is turned off for tests that expect errors. + $this->config->set('Cache.DefinitionImpl', null); + parent::expectError($error); + } + + function test_parseTinyMCEAllowedList() { + + $def = new HTMLPurifier_HTMLDefinition(); + + // note: this is case-sensitive, but its config schema + // counterpart is not. This is generally a good thing for users, + // but it's a slight internal inconsistency + + $this->assertEqual( + $def->parseTinyMCEAllowedList(''), + array(array(), array()) + ); + + $this->assertEqual( + $def->parseTinyMCEAllowedList('a,b,c'), + array(array('a' => true, 'b' => true, 'c' => true), array()) + ); + + $this->assertEqual( + $def->parseTinyMCEAllowedList('a[x|y|z]'), + array(array('a' => true), array('a.x' => true, 'a.y' => true, 'a.z' => true)) + ); + + $this->assertEqual( + $def->parseTinyMCEAllowedList('*[id]'), + array(array(), array('*.id' => true)) + ); + + $this->assertEqual( + $def->parseTinyMCEAllowedList('a[*]'), + array(array('a' => true), array('a.*' => true)) + ); + + $this->assertEqual( + $def->parseTinyMCEAllowedList('span[style],strong,a[href|title]'), + array(array('span' => true, 'strong' => true, 'a' => true), + array('span.style' => true, 'a.href' => true, 'a.title' => true)) + ); + + $this->assertEqual( + // alternate form: + $def->parseTinyMCEAllowedList( +'span[style] +strong +a[href|title] +'), + $val = array(array('span' => true, 'strong' => true, 'a' => true), + array('span.style' => true, 'a.href' => true, 'a.title' => true)) + ); + + $this->assertEqual( + $def->parseTinyMCEAllowedList(' span [ style ], strong'."\n\t".'a[href | title]'), + $val + ); + + } + + function test_Allowed() { + + $config1 = HTMLPurifier_Config::create(array( + 'HTML.AllowedElements' => array('b', 'i', 'p', 'a'), + 'HTML.AllowedAttributes' => array('a@href', '*@id') + )); + + $config2 = HTMLPurifier_Config::create(array( + 'HTML.Allowed' => 'b,i,p,a[href],*[id]' + )); + + $this->assertEqual($config1->getHTMLDefinition(), $config2->getHTMLDefinition()); + + } + + function assertPurification_AllowedElements_p() { + $this->assertPurification('<p><b>Jelly</b></p>', '<p>Jelly</p>'); + } + + function test_AllowedElements() { + $this->config->set('HTML.AllowedElements', 'p'); + $this->assertPurification_AllowedElements_p(); + } + + function test_AllowedElements_multiple() { + $this->config->set('HTML.AllowedElements', 'p,div'); + $this->assertPurification('<div><p><b>Jelly</b></p></div>', '<div><p>Jelly</p></div>'); + } + + function test_AllowedElements_invalidElement() { + $this->config->set('HTML.AllowedElements', 'obviously_invalid,p'); + $this->expectError(new PatternExpectation("/Element 'obviously_invalid' is not supported/")); + $this->assertPurification_AllowedElements_p(); + } + + function test_AllowedElements_invalidElement_xssAttempt() { + $this->config->set('HTML.AllowedElements', '<script>,p'); + $this->expectError(new PatternExpectation("/Element '<script>' is not supported/")); + $this->assertPurification_AllowedElements_p(); + } + + function test_AllowedElements_multipleInvalidElements() { + $this->config->set('HTML.AllowedElements', 'dr-wiggles,dr-pepper,p'); + $this->expectError(new PatternExpectation("/Element 'dr-wiggles' is not supported/")); + $this->expectError(new PatternExpectation("/Element 'dr-pepper' is not supported/")); + $this->assertPurification_AllowedElements_p(); + } + + function assertPurification_AllowedAttributes_global_style() { + $this->assertPurification( + '<p style="font-weight:bold;" class="foo">Jelly</p><br style="clear:both;" />', + '<p style="font-weight:bold;">Jelly</p><br style="clear:both;" />'); + } + + function test_AllowedAttributes_global_preferredSyntax() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', 'style'); + $this->assertPurification_AllowedAttributes_global_style(); + } + + function test_AllowedAttributes_global_verboseSyntax() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', '*@style'); + $this->assertPurification_AllowedAttributes_global_style(); + } + + function test_AllowedAttributes_global_discouragedSyntax() { + // Emit errors eventually + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', '*.style'); + $this->assertPurification_AllowedAttributes_global_style(); + } + + function assertPurification_AllowedAttributes_local_p_style() { + $this->assertPurification( + '<p style="font-weight:bold;" class="foo">Jelly</p><br style="clear:both;" />', + '<p style="font-weight:bold;">Jelly</p><br />'); + } + + function test_AllowedAttributes_local_preferredSyntax() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', 'p@style'); + $this->assertPurification_AllowedAttributes_local_p_style(); + } + + function test_AllowedAttributes_local_discouragedSyntax() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', 'p.style'); + $this->assertPurification_AllowedAttributes_local_p_style(); + } + + function test_AllowedAttributes_multiple() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', 'p@style,br@class,title'); + $this->assertPurification( + '<p style="font-weight:bold;" class="foo" title="foo">Jelly</p><br style="clear:both;" class="foo" title="foo" />', + '<p style="font-weight:bold;" title="foo">Jelly</p><br class="foo" title="foo" />' + ); + } + + function test_AllowedAttributes_local_invalidAttribute() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', array('p@style', 'p@<foo>')); + $this->expectError(new PatternExpectation("/Attribute '<foo>' in element 'p' not supported/")); + $this->assertPurification_AllowedAttributes_local_p_style(); + } + + function test_AllowedAttributes_global_invalidAttribute() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', array('style', '<foo>')); + $this->expectError(new PatternExpectation("/Global attribute '<foo>' is not supported in any elements/")); + $this->assertPurification_AllowedAttributes_global_style(); + } + + function test_AllowedAttributes_local_invalidAttributeDueToMissingElement() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', 'p.style,foo.style'); + $this->expectError(new PatternExpectation("/Cannot allow attribute 'style' if element 'foo' is not allowed\/supported/")); + $this->assertPurification_AllowedAttributes_local_p_style(); + } + + function test_AllowedAttributes_duplicate() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', 'p.style,p@style'); + $this->assertPurification_AllowedAttributes_local_p_style(); + } + + function test_AllowedAttributes_multipleErrors() { + $this->config->set('HTML.AllowedElements', array('p', 'br')); + $this->config->set('HTML.AllowedAttributes', 'p.style,foo.style,<foo>'); + $this->expectError(new PatternExpectation("/Cannot allow attribute 'style' if element 'foo' is not allowed\/supported/")); + $this->expectError(new PatternExpectation("/Global attribute '<foo>' is not supported in any elements/")); + $this->assertPurification_AllowedAttributes_local_p_style(); + } + + function test_ForbiddenElements() { + $this->config->set('HTML.ForbiddenElements', 'b'); + $this->assertPurification('<b>b</b><i>i</i>', 'b<i>i</i>'); + } + + function test_ForbiddenElements_invalidElement() { + $this->config->set('HTML.ForbiddenElements', 'obviously_incorrect'); + // no error! + $this->assertPurification('<i>i</i>'); + } + + function assertPurification_ForbiddenAttributes_b_style() { + $this->assertPurification( + '<b style="float:left;">b</b><i style="float:left;">i</i>', + '<b>b</b><i style="float:left;">i</i>'); + } + + function test_ForbiddenAttributes() { + $this->config->set('HTML.ForbiddenAttributes', 'b@style'); + $this->assertPurification_ForbiddenAttributes_b_style(); + } + + function test_ForbiddenAttributes_incorrectSyntax() { + $this->config->set('HTML.ForbiddenAttributes', 'b.style'); + $this->expectError("Error with b.style: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead"); + $this->assertPurification('<b style="float:left;">Test</b>'); + } + + function test_ForbiddenAttributes_incorrectGlobalSyntax() { + $this->config->set('HTML.ForbiddenAttributes', '*.style'); + $this->expectError("Error with *.style: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead"); + $this->assertPurification('<b style="float:left;">Test</b>'); + } + + function assertPurification_ForbiddenAttributes_style() { + $this->assertPurification( + '<b class="foo" style="float:left;">b</b><i style="float:left;">i</i>', + '<b class="foo">b</b><i>i</i>'); + } + + function test_ForbiddenAttributes_global() { + $this->config->set('HTML.ForbiddenAttributes', 'style'); + $this->assertPurification_ForbiddenAttributes_style(); + } + + function test_ForbiddenAttributes_globalVerboseFormat() { + $this->config->set('HTML.ForbiddenAttributes', '*@style'); + $this->assertPurification_ForbiddenAttributes_style(); + } + + function test_addAttribute() { + + $config = HTMLPurifier_Config::createDefault(); + $def = $config->getHTMLDefinition(true); + $def->addAttribute('span', 'custom', 'Enum#attribute'); + + $purifier = new HTMLPurifier($config); + $input = '<span custom="attribute">Custom!</span>'; + $output = $purifier->purify($input); + $this->assertIdentical($input, $output); + + } + + function test_addAttribute_multiple() { + + $config = HTMLPurifier_Config::createDefault(); + $def = $config->getHTMLDefinition(true); + $def->addAttribute('span', 'custom', 'Enum#attribute'); + $def->addAttribute('span', 'foo', 'Text'); + + $purifier = new HTMLPurifier($config); + $input = '<span custom="attribute" foo="asdf">Custom!</span>'; + $output = $purifier->purify($input); + $this->assertIdentical($input, $output); + + } + + function test_addElement() { + + $config = HTMLPurifier_Config::createDefault(); + $def = $config->getHTMLDefinition(true); + $def->addElement('marquee', 'Inline', 'Inline', 'Common', array('width' => 'Length')); + + $purifier = new HTMLPurifier($config); + $input = '<span><marquee width="50">Foobar</marquee></span>'; + $output = $purifier->purify($input); + $this->assertIdentical($input, $output); + + } + + function test_injector() { + generate_mock_once('HTMLPurifier_Injector'); + $injector = new HTMLPurifier_InjectorMock(); + $injector->name = 'MyInjector'; + $injector->setReturnValue('checkNeeded', false); + + $module = $this->config->getHTMLDefinition(true)->getAnonymousModule(); + $module->info_injector[] = $injector; + + $this->assertIdentical($this->config->getHTMLDefinition()->info_injector, + array( + 'MyInjector' => $injector, + ) + ); + } + + function test_injectorMissingNeeded() { + generate_mock_once('HTMLPurifier_Injector'); + $injector = new HTMLPurifier_InjectorMock(); + $injector->name = 'MyInjector'; + $injector->setReturnValue('checkNeeded', 'a'); + + $module = $this->config->getHTMLDefinition(true)->getAnonymousModule(); + $module->info_injector[] = $injector; + + $this->assertIdentical($this->config->getHTMLDefinition()->info_injector, + array() + ); + } + + function test_injectorIntegration() { + $module = $this->config->getHTMLDefinition(true)->getAnonymousModule(); + $module->info_injector[] = 'Linkify'; + + $this->assertIdentical( + $this->config->getHTMLDefinition()->info_injector, + array('Linkify' => new HTMLPurifier_Injector_Linkify()) + ); + } + + function test_injectorIntegrationFail() { + $this->config->set('HTML.Allowed', 'p'); + + $module = $this->config->getHTMLDefinition(true)->getAnonymousModule(); + $module->info_injector[] = 'Linkify'; + + $this->assertIdentical( + $this->config->getHTMLDefinition()->info_injector, + array() + ); + } + + function test_notAllowedRequiredAttributeError() { + $this->expectError("Required attribute 'src' in element 'img' was not allowed, which means 'img' will not be allowed either"); + $this->config->set('HTML.Allowed', 'img[alt]'); + $this->config->getHTMLDefinition(); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/FormsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/FormsTest.php new file mode 100644 index 000000000..5bc4c99c3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/FormsTest.php @@ -0,0 +1,155 @@ +<?php + +class HTMLPurifier_HTMLModule_FormsTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $this->config->set('HTML.Trusted', true); + $this->config->set('Attr.EnableID', true); + } + + function testBasicUse() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult( // need support for label for later + ' +<form action="http://somesite.com/prog/adduser" method="post"> + <p> + <label>First name: </label> + <input type="text" id="firstname" /><br /> + <label>Last name: </label> + <input type="text" id="lastname" /><br /> + <label>email: </label> + <input type="text" id="email" /><br /> + <input type="radio" name="sex" value="Male" /> Male<br /> + <input type="radio" name="sex" value="Female" /> Female<br /> + <input type="submit" value="Send" /> <input type="reset" /> + </p> +</form>' + ); + } + + function testSelectOption() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult(' +<form action="http://somesite.com/prog/component-select" method="post"> + <p> + <select multiple="multiple" size="4" name="component-select"> + <option selected="selected" value="Component_1_a">Component_1</option> + <option selected="selected" value="Component_1_b">Component_2</option> + <option>Component_3</option> + <option>Component_4</option> + <option>Component_5</option> + <option>Component_6</option> + <option>Component_7</option> + </select> + <input type="submit" value="Send" /><input type="reset" /> + </p> +</form> + '); + } + + function testSelectOptgroup() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult(' +<form action="http://somesite.com/prog/someprog" method="post"> +<p> + <select name="ComOS"> + <option selected="selected" label="none" value="none">None</option> + <optgroup label="PortMaster 3"> + <option label="3.7.1" value="pm3_3.7.1">PortMaster 3 with ComOS 3.7.1</option> + <option label="3.7" value="pm3_3.7">PortMaster 3 with ComOS 3.7</option> + <option label="3.5" value="pm3_3.5">PortMaster 3 with ComOS 3.5</option> + </optgroup> + <optgroup label="PortMaster 2"> + <option label="3.7" value="pm2_3.7">PortMaster 2 with ComOS 3.7</option> + <option label="3.5" value="pm2_3.5">PortMaster 2 with ComOS 3.5</option> + </optgroup> + <optgroup label="IRX"> + <option label="3.7R" value="IRX_3.7R">IRX with ComOS 3.7R</option> + <option label="3.5R" value="IRX_3.5R">IRX with ComOS 3.5R</option> + </optgroup> + </select> +</p> +</form> + '); + } + + function testTextarea() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult(' +<form action="http://somesite.com/prog/text-read" method="post"> + <p> + <textarea name="thetext" rows="20" cols="80"> + First line of initial text. + Second line of initial text. + </textarea> + <input type="submit" value="Send" /><input type="reset" /> + </p> +</form> + '); + } + + // label tests omitted + + function testFieldset() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult(' +<form action="..." method="post"> + <fieldset> + <legend>Personal Information</legend> + Last Name: <input name="personal_lastname" type="text" tabindex="1" /> + First Name: <input name="personal_firstname" type="text" tabindex="2" /> + Address: <input name="personal_address" type="text" tabindex="3" /> + ...more personal information... + </fieldset> + <fieldset> + <legend>Medical History</legend> + <input name="history_illness" type="checkbox" value="Smallpox" tabindex="20" />Smallpox + <input name="history_illness" type="checkbox" value="Mumps" tabindex="21" /> Mumps + <input name="history_illness" type="checkbox" value="Dizziness" tabindex="22" /> Dizziness + <input name="history_illness" type="checkbox" value="Sneezing" tabindex="23" /> Sneezing + ...more medical history... + </fieldset> + <fieldset> + <legend>Current Medication</legend> + Are you currently taking any medication? + <input name="medication_now" type="radio" value="Yes" tabindex="35" />Yes + <input name="medication_now" type="radio" value="No" tabindex="35" />No + + If you are currently taking medication, please indicate + it in the space below: + <textarea name="current_medication" rows="20" cols="50" tabindex="40"></textarea> + </fieldset> +</form> + '); + } + + function testInputTransform() { + $this->config->set('HTML.Doctype', 'XHTML 1.0 Strict'); + $this->assertResult('<input type="checkbox" />', '<input type="checkbox" value="" />'); + } + + function testTextareaTransform() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult('<textarea></textarea>', '<textarea cols="22" rows="3"></textarea>'); + } + + function testTextInFieldset() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult('<fieldset> <legend></legend>foo</fieldset>'); + } + + function testStrict() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult('<form action=""></form>', ''); + } + + function testLegacy() { + $this->assertResult('<form action=""></form>'); + $this->assertResult('<form action=""><input align="left" /></form>'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ImageTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ImageTest.php new file mode 100644 index 000000000..30e36f1d1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ImageTest.php @@ -0,0 +1,55 @@ +<?php + +class HTMLPurifier_HTMLModule_ImageTest extends HTMLPurifier_HTMLModuleHarness +{ + + + function testNormal() { + $this->assertResult('<img height="40" width="40" src="" alt="" />'); + } + + function testLengthTooLarge() { + $this->assertResult( + '<img height="40000" width="40000" src="" alt="" />', + '<img height="1200" width="1200" src="" alt="" />' + ); + } + + function testLengthPercentage() { + $this->assertResult( + '<img height="100%" width="100%" src="" alt="" />', + '<img src="" alt="" />' + ); + } + + function testLengthCustomMax() { + $this->config->set('HTML.MaxImgLength', 20); + $this->assertResult( + '<img height="30" width="30" src="" alt="" />', + '<img height="20" width="20" src="" alt="" />' + ); + } + + function testLengthCrashFixDisabled() { + $this->config->set('HTML.MaxImgLength', null); + $this->assertResult( + '<img height="100%" width="100%" src="" alt="" />' + ); + $this->assertResult( + '<img height="40000" width="40000" src="" alt="" />' + ); + } + + function testLengthTrusted() { + $this->config->set('HTML.Trusted', true); + $this->assertResult( + '<img height="100%" width="100%" src="" alt="" />' + ); + $this->assertResult( + '<img height="40000" width="40000" src="" alt="" />' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NameTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NameTest.php new file mode 100644 index 000000000..9152710bd --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NameTest.php @@ -0,0 +1,32 @@ +<?php + +class HTMLPurifier_HTMLModule_NameTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + } + + function testBasicUse() { + $this->config->set('Attr.EnableID', true); + $this->assertResult( + '<a name="foo">bar</a>' + ); + } + + function testCDATA() { + $this->config->set('HTML.Attr.Name.UseCDATA', true); + $this->assertResult( + '<a name="2">Baz</a><a name="2">Bar</a>' + ); + } + + function testCDATAWithHeavyTidy() { + $this->config->set('HTML.Attr.Name.UseCDATA', true); + $this->config->set('HTML.TidyLevel', 'heavy'); + $this->assertResult('<a name="2">Baz</a>'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NofollowTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NofollowTest.php new file mode 100644 index 000000000..43084de3a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/NofollowTest.php @@ -0,0 +1,26 @@ +<?php + +class HTMLPurifier_HTMLModule_NofollowTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $this->config->set('HTML.Nofollow', true); + } + + function testNofollow() { + $this->assertResult( + '<a href="http://google.com">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>', + '<a href="http://google.com" rel="nofollow">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>' + ); + } + + function testNofollowDupe() { + $this->assertResult( + '<a href="http://google.com" rel="nofollow">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ObjectTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ObjectTest.php new file mode 100644 index 000000000..f824d6021 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ObjectTest.php @@ -0,0 +1,38 @@ +<?php + +class HTMLPurifier_HTMLModule_ObjectTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $this->config->set('HTML.Trusted', true); + } + + function testDefaultRemoval() { + $this->config->set('HTML.Trusted', false); + $this->assertResult( + '<object></object>', '' + ); + } + + function testMinimal() { + $this->assertResult('<object></object>'); + } + + function testStandardUseCase() { + $this->assertResult( +'<object type="video/x-ms-wmv" data="http://domain.com/video.wmv" width="320" height="256"> +<param name="src" value="http://domain.com/video.wmv" /> +<param name="autostart" value="false" /> +<param name="controller" value="true" /> +<param name="pluginurl" value="http://www.microsoft.com/Windows/MediaPlayer/" /> +<a href="http://www.microsoft.com/Windows/MediaPlayer/">Windows Media player required</a> +</object>' + ); + } + + // more test-cases? + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ProprietaryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ProprietaryTest.php new file mode 100644 index 000000000..21ebe8a5b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ProprietaryTest.php @@ -0,0 +1,30 @@ +<?php + +class HTMLPurifier_HTMLModule_ProprietaryTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $this->config->set('HTML.Proprietary', true); + } + + function testMarquee() { + $this->assertResult( + '<span><marquee + width="20%" + height="34" + direction="left" + behavior="alternate" + scrolldelay="3" + scrollamount="5" + loop="4" + bgcolor="#FF0000" + hspace="5" + vspace="3" + ><div>Block</div><span>Inline</span>Text</marquee></span>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/RubyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/RubyTest.php new file mode 100644 index 000000000..23a1400d0 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/RubyTest.php @@ -0,0 +1,55 @@ +<?php + +class HTMLPurifier_HTMLModule_RubyTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $this->config->set('HTML.Doctype', 'XHTML 1.1'); + } + + function testBasicUse() { + $this->assertResult( + '<ruby><rb>WWW</rb><rt>World Wide Web</rt></ruby>' + ); + } + + function testRPUse() { + $this->assertResult( + '<ruby><rb>WWW</rb><rp>(</rp><rt>World Wide Web</rt><rp>)</rp></ruby>' + ); + } + + function testComplexUse() { + $this->assertResult( +'<ruby> + <rbc> + <rb>10</rb> + <rb>31</rb> + <rb>2002</rb> + </rbc> + <rtc> + <rt>Month</rt> + <rt>Day</rt> + <rt>Year</rt> + </rtc> + <rtc> + <rt rbspan="3">Expiration Date</rt> + </rtc> +</ruby>' + ); + + /* not implemented + function testBackwardsCompat() { + $this->assertResult( + '<ruby>A<rp>(</rp><rt>aaa</rt><rp>)</rp></ruby>', + '<ruby><rb>A</rb><rp>(</rp><rt>aaa</rt><rp>)</rp></ruby>' + ); + } + */ + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeEmbedTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeEmbedTest.php new file mode 100644 index 000000000..779d34857 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeEmbedTest.php @@ -0,0 +1,41 @@ +<?php + +class HTMLPurifier_HTMLModule_SafeEmbedTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $def = $this->config->getHTMLDefinition(true); + $def->manager->addModule('SafeEmbed'); + } + + function testMinimal() { + $this->assertResult( + '<embed src="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" />', + '<embed src="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" allowscriptaccess="never" allownetworking="internal" type="application/x-shockwave-flash" />' + ); + } + + function testYouTube() { + $this->assertResult( + '<embed src="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed>', + '<embed src="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" type="application/x-shockwave-flash" width="425" height="344" allowscriptaccess="never" allownetworking="internal" />' + ); + } + + function testMalicious() { + $this->assertResult( + '<embed src="http://example.com/bad.swf" type="application/x-shockwave-flash" width="9999999" height="3499994" allowscriptaccess="always" allownetworking="always" />', + '<embed src="http://example.com/bad.swf" type="application/x-shockwave-flash" width="1200" height="1200" allowscriptaccess="never" allownetworking="internal" />' + ); + } + + function testFull() { + $this->assertResult( + '<b><embed src="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" type="application/x-shockwave-flash" width="24" height="23" allowscriptaccess="never" allownetworking="internal" wmode="window" /></b>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php new file mode 100644 index 000000000..d8a23d50f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php @@ -0,0 +1,49 @@ +<?php + +class HTMLPurifier_HTMLModule_SafeObjectTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $this->config->set('HTML.DefinitionID', 'HTMLPurifier_HTMLModule_SafeObjectTest'); + $this->config->set('HTML.SafeObject', true); + } + + function testMinimal() { + $this->assertResult( + '<object></object>', + '<object type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>' + ); + } + + function testYouTube() { + // embed is purposely removed + $this->assertResult( + '<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&hl=en"></param><embed src="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed></object>', + '<object width="425" height="344" data="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" /></object>' + ); + } + + function testMalicious() { + $this->assertResult( + '<object width="9999999" height="9999999"><param name="allowScriptAccess" value="always" /><param name="movie" value="http://example.com/attack.swf" /></object>', + '<object width="1200" height="1200" data="http://example.com/attack.swf" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://example.com/attack.swf" /></object>' + ); + } + + function testFull() { + $this->assertResult( + '<b><object width="425" height="344" type="application/x-shockwave-flash" data="Foobar"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="flashvars" value="foobarbaz=bally" /><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" /><param name="wmode" value="window" /></object></b>' + ); + } + + function testFullScreen() { + $this->config->set('HTML.FlashAllowFullScreen', true); + $this->assertResult( + '<b><object width="425" height="344" type="application/x-shockwave-flash" data="Foobar"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="flashvars" value="foobarbaz=bally" /><param name="movie" value="http://www.youtube.com/v/RVtEQxH7PWA&hl=en" /><param name="wmode" value="window" /><param name="allowFullScreen" value="true" /></object></b>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ScriptingTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ScriptingTest.php new file mode 100644 index 000000000..c844a4776 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/ScriptingTest.php @@ -0,0 +1,55 @@ +<?php + +class HTMLPurifier_HTMLModule_ScriptingTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $this->config->set('HTML.Trusted', true); + $this->config->set('Output.CommentScriptContents', false); + } + + function testDefaultRemoval() { + $this->config->set('HTML.Trusted', false); + $this->assertResult( + '<script type="text/javascript">foo();</script>', '' + ); + } + + function testPreserve() { + $this->assertResult( + '<script type="text/javascript">foo();</script>' + ); + } + + function testCDATAEnclosure() { + $this->assertResult( +'<script type="text/javascript">//<![CDATA[ +alert("<This is compatible with XHTML>"); +//]]></script>' + ); + } + + function testAllAttributes() { + $this->assertResult( + '<script + defer="defer" + src="test.js" + type="text/javascript" + >PCDATA</script>' + ); + } + + function testUnsupportedAttributes() { + $this->assertResult( + '<script + type="text/javascript" + charset="utf-8" + >PCDATA</script>', + '<script type="text/javascript">PCDATA</script>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TargetBlankTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TargetBlankTest.php new file mode 100644 index 000000000..a757fecb3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TargetBlankTest.php @@ -0,0 +1,20 @@ +<?php + +class HTMLPurifier_HTMLModule_TargetBlankTest extends HTMLPurifier_HTMLModuleHarness +{ + + function setUp() { + parent::setUp(); + $this->config->set('HTML.TargetBlank', true); + } + + function testTargetBlank() { + $this->assertResult( + '<a href="http://google.com">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>', + '<a href="http://google.com" target="blank">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TidyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TidyTest.php new file mode 100644 index 000000000..e80aeb16d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModule/TidyTest.php @@ -0,0 +1,224 @@ +<?php + +Mock::generatePartial( + 'HTMLPurifier_HTMLModule_Tidy', + 'HTMLPurifier_HTMLModule_Tidy_TestForConstruct', + array('makeFixes', 'makeFixesForLevel', 'populate') +); + +class HTMLPurifier_HTMLModule_TidyTest extends HTMLPurifier_Harness +{ + + function test_getFixesForLevel() { + + $module = new HTMLPurifier_HTMLModule_Tidy(); + $module->fixesForLevel['light'][] = 'light-fix'; + $module->fixesForLevel['medium'][] = 'medium-fix'; + $module->fixesForLevel['heavy'][] = 'heavy-fix'; + + $this->assertIdentical( + array(), + $module->getFixesForLevel('none') + ); + $this->assertIdentical( + array('light-fix' => true), + $module->getFixesForLevel('light') + ); + $this->assertIdentical( + array('light-fix' => true, 'medium-fix' => true), + $module->getFixesForLevel('medium') + ); + $this->assertIdentical( + array('light-fix' => true, 'medium-fix' => true, 'heavy-fix' => true), + $module->getFixesForLevel('heavy') + ); + + $this->expectError('Tidy level turbo not recognized'); + $module->getFixesForLevel('turbo'); + + } + + function test_setup() { + + $i = 0; // counter, helps us isolate expectations + + // initialize partial mock + $module = new HTMLPurifier_HTMLModule_Tidy_TestForConstruct(); + $module->fixesForLevel['light'] = array('light-fix-1', 'light-fix-2'); + $module->fixesForLevel['medium'] = array('medium-fix-1', 'medium-fix-2'); + $module->fixesForLevel['heavy'] = array('heavy-fix-1', 'heavy-fix-2'); + + $j = 0; + $fixes = array( + 'light-fix-1' => $lf1 = $j++, + 'light-fix-2' => $lf2 = $j++, + 'medium-fix-1' => $mf1 = $j++, + 'medium-fix-2' => $mf2 = $j++, + 'heavy-fix-1' => $hf1 = $j++, + 'heavy-fix-2' => $hf2 = $j++ + ); + $module->setReturnValue('makeFixes', $fixes); + + $config = HTMLPurifier_Config::create(array( + 'HTML.TidyLevel' => 'none' + )); + $module->expectAt($i++, 'populate', array(array())); + $module->setup($config); + + // basic levels + + $config = HTMLPurifier_Config::create(array( + 'HTML.TidyLevel' => 'light' + )); + $module->expectAt($i++, 'populate', array(array( + 'light-fix-1' => $lf1, + 'light-fix-2' => $lf2 + ))); + $module->setup($config); + + $config = HTMLPurifier_Config::create(array( + 'HTML.TidyLevel' => 'heavy' + )); + $module->expectAt($i++, 'populate', array(array( + 'light-fix-1' => $lf1, + 'light-fix-2' => $lf2, + 'medium-fix-1' => $mf1, + 'medium-fix-2' => $mf2, + 'heavy-fix-1' => $hf1, + 'heavy-fix-2' => $hf2 + ))); + $module->setup($config); + + // fine grained tuning + + $config = HTMLPurifier_Config::create(array( + 'HTML.TidyLevel' => 'none', + 'HTML.TidyAdd' => array('light-fix-1', 'medium-fix-1') + )); + $module->expectAt($i++, 'populate', array(array( + 'light-fix-1' => $lf1, + 'medium-fix-1' => $mf1 + ))); + $module->setup($config); + + $config = HTMLPurifier_Config::create(array( + 'HTML.TidyLevel' => 'medium', + 'HTML.TidyRemove' => array('light-fix-1', 'medium-fix-1') + )); + $module->expectAt($i++, 'populate', array(array( + 'light-fix-2' => $lf2, + 'medium-fix-2' => $mf2 + ))); + $module->setup($config); + + } + + function test_makeFixesForLevel() { + + $module = new HTMLPurifier_HTMLModule_Tidy(); + $module->defaultLevel = 'heavy'; + + $module->makeFixesForLevel(array( + 'fix-1' => 0, + 'fix-2' => 1, + 'fix-3' => 2 + )); + + $this->assertIdentical($module->fixesForLevel['heavy'], array('fix-1', 'fix-2', 'fix-3')); + $this->assertIdentical($module->fixesForLevel['medium'], array()); + $this->assertIdentical($module->fixesForLevel['light'], array()); + + } + function test_makeFixesForLevel_undefinedLevel() { + + $module = new HTMLPurifier_HTMLModule_Tidy(); + $module->defaultLevel = 'bananas'; + + $this->expectError('Default level bananas does not exist'); + + $module->makeFixesForLevel(array( + 'fix-1' => 0 + )); + + } + + function test_getFixType() { + + // syntax needs documenting + + $module = new HTMLPurifier_HTMLModule_Tidy(); + + $this->assertIdentical( + $module->getFixType('a'), + array('tag_transform', array('element' => 'a')) + ); + + $this->assertIdentical( + $module->getFixType('a@href'), + $reuse = array('attr_transform_pre', array('element' => 'a', 'attr' => 'href')) + ); + + $this->assertIdentical( + $module->getFixType('a@href#pre'), + $reuse + ); + + $this->assertIdentical( + $module->getFixType('a@href#post'), + array('attr_transform_post', array('element' => 'a', 'attr' => 'href')) + ); + + $this->assertIdentical( + $module->getFixType('xml:foo@xml:bar'), + array('attr_transform_pre', array('element' => 'xml:foo', 'attr' => 'xml:bar')) + ); + + $this->assertIdentical( + $module->getFixType('blockquote#child'), + array('child', array('element' => 'blockquote')) + ); + + $this->assertIdentical( + $module->getFixType('@lang'), + array('attr_transform_pre', array('attr' => 'lang')) + ); + + $this->assertIdentical( + $module->getFixType('@lang#post'), + array('attr_transform_post', array('attr' => 'lang')) + ); + + } + + function test_populate() { + + $i = 0; + + $module = new HTMLPurifier_HTMLModule_Tidy(); + $module->populate(array( + 'element' => $element = $i++, + 'element@attr' => $attr = $i++, + 'element@attr#post' => $attr_post = $i++, + 'element#child' => $child = $i++, + 'element#content_model_type' => $content_model_type = $i++, + '@attr' => $global_attr = $i++, + '@attr#post' => $global_attr_post = $i++ + )); + + $module2 = new HTMLPurifier_HTMLModule_Tidy(); + $e = $module2->addBlankElement('element'); + $e->attr_transform_pre['attr'] = $attr; + $e->attr_transform_post['attr'] = $attr_post; + $e->child = $child; + $e->content_model_type = $content_model_type; + $module2->info_tag_transform['element'] = $element; + $module2->info_attr_transform_pre['attr'] = $global_attr; + $module2->info_attr_transform_post['attr'] = $global_attr_post; + + $this->assertEqual($module, $module2); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleHarness.php new file mode 100644 index 000000000..7aadf468a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleHarness.php @@ -0,0 +1,11 @@ +<?php + +class HTMLPurifier_HTMLModuleHarness extends HTMLPurifier_StrategyHarness +{ + function setup() { + parent::setup(); + $this->obj = new HTMLPurifier_Strategy_Core(); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleManagerTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleManagerTest.php new file mode 100644 index 000000000..2750f8924 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleManagerTest.php @@ -0,0 +1,120 @@ +<?php + +class HTMLPurifier_HTMLModuleManagerTest extends HTMLPurifier_Harness +{ + + protected function createManager() { + $manager = new HTMLPurifier_HTMLModuleManager(); + + $this->config->set('HTML.CustomDoctype', 'Blank'); + $manager->doctypes->register('Blank'); + + $attrdef_nmtokens = new HTMLPurifier_AttrDef_HTML_Nmtokens(); + + generate_mock_once('HTMLPurifier_AttrDef'); + $attrdef = new HTMLPurifier_AttrDefMock(); + $attrdef->setReturnValue('make', $attrdef_nmtokens); + $manager->attrTypes->set('NMTOKENS', $attrdef); + return $manager; + } + + function test_addModule() { + + $manager = $this->createManager(); + + // ...but we add user modules + + $common_module = new HTMLPurifier_HTMLModule(); + $common_module->name = 'Common'; + $common_module->attr_collections['Common'] = array('class' => 'NMTOKENS'); + $common_module->content_sets['Flow'] = 'Block | Inline'; + $manager->addModule($common_module); + + $structural_module = new HTMLPurifier_HTMLModule(); + $structural_module->name = 'Structural'; + $structural_module->addElement('p', 'Block', 'Inline', 'Common'); + $manager->addModule($structural_module); + + $formatting_module = new HTMLPurifier_HTMLModule(); + $formatting_module->name = 'Formatting'; + $formatting_module->addElement('em', 'Inline', 'Inline', 'Common'); + $manager->addModule($formatting_module); + + $unsafe_module = new HTMLPurifier_HTMLModule(); + $unsafe_module->name = 'Unsafe'; + $unsafe_module->safe = false; + $unsafe_module->addElement('div', 'Block', 'Flow'); + $manager->addModule($unsafe_module); + + $config = HTMLPurifier_Config::createDefault(); + $config->set('HTML.Trusted', false); + $config->set('HTML.CustomDoctype', 'Blank'); + + $manager->setup($config); + + $attrdef_nmtokens = new HTMLPurifier_AttrDef_HTML_Nmtokens(); + + $p = new HTMLPurifier_ElementDef(); + $p->attr['class'] = $attrdef_nmtokens; + $p->child = new HTMLPurifier_ChildDef_Optional(array('em', '#PCDATA')); + $p->content_model = 'em | #PCDATA'; + $p->content_model_type = 'optional'; + $p->descendants_are_inline = true; + + $em = new HTMLPurifier_ElementDef(); + $em->attr['class'] = $attrdef_nmtokens; + $em->child = new HTMLPurifier_ChildDef_Optional(array('em', '#PCDATA')); + $em->content_model = 'em | #PCDATA'; + $em->content_model_type = 'optional'; + $em->descendants_are_inline = true; + + $this->assertEqual( + array('p' => $p, 'em' => $em), + $manager->getElements() + ); + + // test trusted parameter override + + $div = new HTMLPurifier_ElementDef(); + $div->child = new HTMLPurifier_ChildDef_Optional(array('p', 'div', 'em', '#PCDATA')); + $div->content_model = 'p | div | em | #PCDATA'; + $div->content_model_type = 'optional'; + $div->descendants_are_inline = false; + + $this->assertEqual($div, $manager->getElement('div', true)); + + } + + function testAllowedModules() { + + $manager = new HTMLPurifier_HTMLModuleManager(); + $manager->doctypes->register( + 'Fantasy Inventory 1.0', true, + array('Weapons', 'Magic') + ); + + // register these modules so it doesn't blow up + $weapons_module = new HTMLPurifier_HTMLModule(); + $weapons_module->name = 'Weapons'; + $manager->registerModule($weapons_module); + + $magic_module = new HTMLPurifier_HTMLModule(); + $magic_module->name = 'Magic'; + $manager->registerModule($magic_module); + + $config = HTMLPurifier_Config::create(array( + 'HTML.CustomDoctype' => 'Fantasy Inventory 1.0', + 'HTML.AllowedModules' => 'Weapons' + )); + $manager->setup($config); + + $this->assertTrue( isset($manager->modules['Weapons'])); + $this->assertFalse(isset($manager->modules['Magic'])); + + } + + + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleTest.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleTest.php new file mode 100644 index 000000000..65220f08b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLModuleTest.php @@ -0,0 +1,146 @@ +<?php + +class HTMLPurifier_HTMLModuleTest extends HTMLPurifier_Harness +{ + + function test_addElementToContentSet() { + + $module = new HTMLPurifier_HTMLModule(); + + $module->addElementToContentSet('b', 'Inline'); + $this->assertIdentical($module->content_sets, array('Inline' => 'b')); + + $module->addElementToContentSet('i', 'Inline'); + $this->assertIdentical($module->content_sets, array('Inline' => 'b | i')); + + } + + function test_addElement() { + + $module = new HTMLPurifier_HTMLModule(); + $def = $module->addElement( + 'a', 'Inline', 'Optional: #PCDATA', array('Common'), + array( + 'href' => 'URI' + ) + ); + + $module2 = new HTMLPurifier_HTMLModule(); + $def2 = new HTMLPurifier_ElementDef(); + $def2->content_model = '#PCDATA'; + $def2->content_model_type = 'optional'; + $def2->attr = array( + 'href' => 'URI', + 0 => array('Common') + ); + $module2->info['a'] = $def2; + $module2->elements = array('a'); + $module2->content_sets['Inline'] = 'a'; + + $this->assertIdentical($module, $module2); + $this->assertIdentical($def, $def2); + $this->assertReference($def, $module->info['a']); + + } + + function test_parseContents() { + + $module = new HTMLPurifier_HTMLModule(); + + // pre-defined templates + $this->assertIdentical( + $module->parseContents('Inline'), + array('optional', 'Inline | #PCDATA') + ); + $this->assertIdentical( + $module->parseContents('Flow'), + array('optional', 'Flow | #PCDATA') + ); + $this->assertIdentical( + $module->parseContents('Empty'), + array('empty', '') + ); + + // normalization procedures + $this->assertIdentical( + $module->parseContents('optional: a'), + array('optional', 'a') + ); + $this->assertIdentical( + $module->parseContents('OPTIONAL :a'), + array('optional', 'a') + ); + $this->assertIdentical( + $module->parseContents('Optional: a'), + array('optional', 'a') + ); + + // others + $this->assertIdentical( + $module->parseContents('Optional: a | b | c'), + array('optional', 'a | b | c') + ); + + // object pass-through + generate_mock_once('HTMLPurifier_AttrDef'); + $this->assertIdentical( + $module->parseContents(new HTMLPurifier_AttrDefMock()), + array(null, null) + ); + + } + + function test_mergeInAttrIncludes() { + + $module = new HTMLPurifier_HTMLModule(); + + $attr = array(); + $module->mergeInAttrIncludes($attr, 'Common'); + $this->assertIdentical($attr, array(0 => array('Common'))); + + $attr = array('a' => 'b'); + $module->mergeInAttrIncludes($attr, array('Common', 'Good')); + $this->assertIdentical($attr, array('a' => 'b', 0 => array('Common', 'Good'))); + + } + + function test_addBlankElement() { + + $module = new HTMLPurifier_HTMLModule(); + $def = $module->addBlankElement('a'); + + $def2 = new HTMLPurifier_ElementDef(); + $def2->standalone = false; + + $this->assertReference($module->info['a'], $def); + $this->assertIdentical($def, $def2); + + } + + function test_makeLookup() { + + $module = new HTMLPurifier_HTMLModule(); + + $this->assertIdentical( + $module->makeLookup('foo'), + array('foo' => true) + ); + $this->assertIdentical( + $module->makeLookup(array('foo')), + array('foo' => true) + ); + + $this->assertIdentical( + $module->makeLookup('foo', 'two'), + array('foo' => true, 'two' => true) + ); + $this->assertIdentical( + $module->makeLookup(array('foo', 'two')), + array('foo' => true, 'two' => true) + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT.php b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT.php new file mode 100644 index 000000000..06e7958fb --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT.php @@ -0,0 +1,33 @@ +<?php + +class HTMLPurifier_HTMLT extends HTMLPurifier_Harness +{ + protected $path; + + public function __construct($path) { + $this->path = $path; + parent::__construct($path); + } + + public function testHtmlt() { + $parser = new HTMLPurifier_StringHashParser(); + $hash = $parser->parseFile($this->path); // assume parser normalizes to "\n" + if (isset($hash['SKIPIF'])) { + if (eval($hash['SKIPIF'])) return; + } + $this->config->set('Output.Newline', "\n"); + if (isset($hash['INI'])) { + // there should be a more efficient way than writing another + // ini file every time... probably means building a parser for + // ini (check out the yaml implementation we saw somewhere else) + $ini_file = $this->path . '.ini'; + file_put_contents($ini_file, $hash['INI']); + $this->config->loadIni($ini_file); + } + $expect = isset($hash['EXPECT']) ? $hash['EXPECT'] : $hash['HTML']; + $this->assertPurification(rtrim($hash['HTML']), rtrim($expect)); + if (isset($hash['INI'])) unlink($ini_file); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-preserve.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-preserve.htmlt new file mode 100644 index 000000000..650f00b6b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-preserve.htmlt @@ -0,0 +1,8 @@ +--INI-- +HTML.AllowedElements = b,i,p,a +HTML.AllowedAttributes = a.href,*.id +--HTML-- +<p>Par.</p> +<p>Para<a href="http://google.com/">gr</a>aph</p> +Text<b>Bol<i>d</i></b> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-remove.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-remove.htmlt new file mode 100644 index 000000000..2b6b8eea6 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/allowed-remove.htmlt @@ -0,0 +1,8 @@ +--INI-- +HTML.AllowedElements = b,i,p,a +HTML.AllowedAttributes = a.href,*.id +--HTML-- +<span>Not allowed</span><a class="mef" id="foobar">Remove id too!</a> +--EXPECT-- +Not allowed<a>Remove id too!</a> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/basic.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/basic.htmlt new file mode 100644 index 000000000..4c1f42778 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/basic.htmlt @@ -0,0 +1,5 @@ +--HTML-- +<b>basic</b> +--EXPECT-- +<b>basic</b> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-preserve.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-preserve.htmlt new file mode 100644 index 000000000..d8882fe22 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-preserve.htmlt @@ -0,0 +1,6 @@ +--INI-- +HTML.ForbiddenElements = b +HTML.ForbiddenAttributes = a@href +--HTML-- +<p>foo</p> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-remove.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-remove.htmlt new file mode 100644 index 000000000..1b5985873 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/blacklist-remove.htmlt @@ -0,0 +1,8 @@ +--INI-- +HTML.ForbiddenElements = b +HTML.ForbiddenAttributes = a@href +--HTML-- +<b>Foo<a href="bar">bar</a></b> +--EXPECT-- +Foo<a>bar</a> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-preserve.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-preserve.htmlt new file mode 100644 index 000000000..a5fe3f849 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-preserve.htmlt @@ -0,0 +1,5 @@ +--INI-- +CSS.AllowedProperties = color,background-color +--HTML-- +<div style="color:#f00;background-color:#ded;">red</div> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-remove.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-remove.htmlt new file mode 100644 index 000000000..644b7001a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/css-allowed-remove.htmlt @@ -0,0 +1,7 @@ +--INI-- +CSS.AllowedProperties = color,background-color +--HTML-- +<div style="color:#f00;border:1px solid #000">red</div> +--EXPECT-- +<div style="color:#f00;">red</div> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/disable-uri.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/disable-uri.htmlt new file mode 100644 index 000000000..fa692e444 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/disable-uri.htmlt @@ -0,0 +1,6 @@ +--INI-- +URI.Disable = true +--HTML-- +<img src="foobar" /> +--EXPECT-- +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/double-youtube.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/double-youtube.htmlt new file mode 100644 index 000000000..a46ce4736 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/double-youtube.htmlt @@ -0,0 +1,6 @@ +--INI-- +HTML.SafeObject = true +Output.FlashCompat = true +--HTML-- +<object width="425" height="350" data="http://www.youtube.com/v/BdU--T8rLns" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/BdU--T8rLns" /><param name="wmode" value="window" /></object> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/empty.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/empty.htmlt new file mode 100644 index 000000000..9094c47b0 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/empty.htmlt @@ -0,0 +1,6 @@ +--INI-- + +--HTML-- + +--EXPECT-- +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/file-uri.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/file-uri.htmlt new file mode 100644 index 000000000..5b9e34a8c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/file-uri.htmlt @@ -0,0 +1,5 @@ +--INI-- +URI.AllowedSchemes = file +--HTML-- +<a href="file:///foo">foo</a> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-default.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-default.htmlt new file mode 100644 index 000000000..1c1460f2c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-default.htmlt @@ -0,0 +1,5 @@ +--HTML-- +<span id="moon">foobar</span> +--EXPECT-- +<span>foobar</span> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-enabled.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-enabled.htmlt new file mode 100644 index 000000000..70ddcf99f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-enabled.htmlt @@ -0,0 +1,6 @@ +--INI-- +Attr.EnableID = true +--HTML-- +<span id="moon">foobar</span> +<img id="folly" src="folly.png" alt="Omigosh!" /> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-img.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-img.htmlt new file mode 100644 index 000000000..12755bea3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-img.htmlt @@ -0,0 +1,8 @@ +--INI-- +Attr.EnableID = true +Core.LexerImpl = DirectLex +--HTML-- +<img src="img_11775.jpg" alt="[Img #11775]" id="EMBEDDED_IMG_11775" > +--EXPECT-- +<img src="img_11775.jpg" alt="[Img #11775]" id="EMBEDDED_IMG_11775" /> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-name-mix.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-name-mix.htmlt new file mode 100644 index 000000000..a48da8e62 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/id-name-mix.htmlt @@ -0,0 +1,11 @@ +--INI-- +Attr.EnableID = true +--HTML-- +<a name="foo" id="foo">Test</a> +<a name="foo">Test2</a> +<a name="bar" id="baz">Test3</a> +--EXPECT-- +<a name="foo" id="foo">Test</a> +<a>Test2</a> +<a name="bar" id="baz">Test3</a> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-list-loop.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-list-loop.htmlt new file mode 100644 index 000000000..0a63e8955 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-list-loop.htmlt @@ -0,0 +1,5 @@ +--HTML-- +<i><ul></ul></i> +--EXPECT-- +<i></i><i></i> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-wraps-block.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-wraps-block.htmlt new file mode 100644 index 000000000..da6bae689 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/inline-wraps-block.htmlt @@ -0,0 +1,5 @@ +--HTML-- +<a href="foo.html"><h1>Foobar</h1></a> +--EXPECT-- +<a href="foo.html"></a><h1><a href="foo.html">Foobar</a></h1><a href="foo.html"></a> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/list-nesting.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/list-nesting.htmlt new file mode 100644 index 000000000..22ebf6058 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/list-nesting.htmlt @@ -0,0 +1,5 @@ +--HTML-- +<ul><li>Sublist 1</li><ul><li>Bullet</li></ul></ul> +--EXPECT-- +<ul><li>Sublist 1<ul><li>Bullet</li></ul></li></ul> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge-extra.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge-extra.htmlt new file mode 100644 index 000000000..4b1c70a9d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge-extra.htmlt @@ -0,0 +1,11 @@ +--INI-- +URI.Munge = "/redirect?s=%s&t=%t&r=%r&n=%n&m=%m&p=%p" +URI.MungeSecretKey = "foo" +URI.MungeResources = true +--HTML-- +<a href="http://example.com">Link</a> +<img src="http://example.com" style="background-image:url(http://example.com);" alt="example.com" /> +--EXPECT-- +<a href="/redirect?s=http%3A%2F%2Fexample.com&t=c15354f3953dfec262c55b1403067e0d045a3059&r=&n=a&m=href&p=">Link</a> +<img src="/redirect?s=http%3A%2F%2Fexample.com&t=c15354f3953dfec262c55b1403067e0d045a3059&r=1&n=img&m=src&p=" style="background-image:url("/redirect?s=http%3A%2F%2Fexample.com&t=c15354f3953dfec262c55b1403067e0d045a3059&r=1&n=img&m=style&p=background-image");" alt="example.com" /> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge.htmlt new file mode 100644 index 000000000..827c216e6 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/munge.htmlt @@ -0,0 +1,52 @@ +--INI-- +URI.Munge = "/r/%s" +URI.AllowedSchemes = http,ftp,file +--HTML-- +<a href="google.com">foo</a> +<a href="/google.com">foo</a> +<a href="//google.com">foo</a> +<a href="///google.com">foo</a> +<a href="////google.com">foo</a> + +<a href="http:google.com">foo</a> +<a href="http:/google.com">foo</a> +<a href="http://google.com">foo</a> +<a href="http:///google.com">foo</a> +<a href="http:////google.com">foo</a> + +<a href="ftp:google.com">foo</a> +<a href="ftp:/google.com">foo</a> +<a href="ftp://google.com">foo</a> +<a href="ftp:///google.com">foo</a> +<a href="ftp:////google.com">foo</a> + +<a href="file:google.com">foo</a> +<a href="file:/google.com">foo</a> +<a href="file://google.com">foo</a> +<a href="file:///google.com">foo</a> +<a href="file:////google.com">foo</a> +--EXPECT-- +<a href="google.com">foo</a> +<a href="/google.com">foo</a> +<a href="/r/%2F%2Fgoogle.com">foo</a> +<a href="/google.com">foo</a> +<a>foo</a> + +<a href="google.com">foo</a> +<a href="/google.com">foo</a> +<a href="/r/http%3A%2F%2Fgoogle.com">foo</a> +<a href="/google.com">foo</a> +<a>foo</a> + +<a>foo</a> +<a>foo</a> +<a href="/r/ftp%3A%2F%2Fgoogle.com">foo</a> +<a>foo</a> +<a>foo</a> + +<a href="file:google.com">foo</a> +<a href="file:/google.com">foo</a> +<a href="file://google.com">foo</a> +<a href="file:///google.com">foo</a> +<a href="file:////google.com">foo</a> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/name.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/name.htmlt new file mode 100644 index 000000000..1713ed417 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/name.htmlt @@ -0,0 +1,6 @@ +--INI-- +Attr.EnableID = true +HTML.Doctype = "XHTML 1.0 Strict" +--HTML-- +<a name="asdf"></a> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt new file mode 100644 index 000000000..40fac62d5 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-googlemaps.htmlt @@ -0,0 +1,8 @@ +--INI-- +HTML.SafeIframe = true +URI.SafeIframeRegexp = "%^http://maps.google.com/%" +--HTML-- +<iframe width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://maps.google.com/?ie=UTF8&ll=37.0625,-95.677068&spn=24.455808,37.353516&z=4&output=embed"></iframe> +--EXPECT-- +<iframe width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://maps.google.com/?ie=UTF8&ll=37.0625,-95.677068&spn=24.455808,37.353516&z=4&output=embed"></iframe> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-invalid.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-invalid.htmlt new file mode 100644 index 000000000..5b366da54 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-invalid.htmlt @@ -0,0 +1,7 @@ +--INI-- +HTML.SafeIframe = true +--HTML-- +<iframe src="http://www.example.com/"></iframe> +--EXPECT-- +<iframe></iframe> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt new file mode 100644 index 000000000..1abc2c827 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe-youtube.htmlt @@ -0,0 +1,8 @@ +--INI-- +HTML.SafeIframe = true +URI.SafeIframeRegexp = "%^http://www.youtube.com/embed/%" +--HTML-- +<iframe title="YouTube video player" width="480" height="390" src="http://www.youtube.com/embed/RVtEQxH7PWA" frameborder="0" allowfullscreen></iframe> +--EXPECT-- +<iframe title="YouTube video player" width="480" height="390" src="http://www.youtube.com/embed/RVtEQxH7PWA" frameborder="0"></iframe> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt new file mode 100644 index 000000000..7c0b60d2f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-iframe.htmlt @@ -0,0 +1,14 @@ +--INI-- +HTML.SafeIframe = true +URI.SafeIframeRegexp = "%(^http://www.example.com/|^https?://dev.example.com/)%" +--HTML-- +<iframe src="http://www.example.com/"></iframe> +<iframe src="http://malicious.host.com/?http://www.example.com/"></iframe> +<iframe src="http://dev.example.com/"></iframe> +<iframe src="https://dev.example.com/"></iframe> +--EXPECT-- +<iframe src="http://www.example.com/"></iframe> +<iframe></iframe> +<iframe src="http://dev.example.com/"></iframe> +<iframe src="https://dev.example.com/"></iframe> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed-munge.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed-munge.htmlt new file mode 100644 index 000000000..77b32d3a1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed-munge.htmlt @@ -0,0 +1,10 @@ +--INI-- +HTML.SafeObject = true +HTML.SafeEmbed = true +URI.Munge = "/redirect.php?url=%s&check=%t" +URI.MungeSecretKey = "foo" +--HTML-- +<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en"></param><embed src="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed></object> +--EXPECT-- +<object width="425" height="344" data="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" /><embed src="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" type="application/x-shockwave-flash" width="425" height="344" allowscriptaccess="never" allownetworking="internal" /></object> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed.htmlt new file mode 100644 index 000000000..f4bb0ff09 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/safe-object-embed.htmlt @@ -0,0 +1,8 @@ +--INI-- +HTML.SafeObject = true +HTML.SafeEmbed = true +--HTML-- +<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en"></param><embed src="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed></object> +--EXPECT-- +<object width="425" height="344" data="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" type="application/x-shockwave-flash"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" /><embed src="http://www.youtube.com/v/Oq3FV_zdyy0&hl=en" type="application/x-shockwave-flash" width="425" height="344" allowscriptaccess="never" allownetworking="internal" /></object> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-bare.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-bare.htmlt new file mode 100644 index 000000000..f85c2fb5e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-bare.htmlt @@ -0,0 +1,9 @@ +--INI-- +HTML.Trusted = true +--HTML-- +<script type="text/javascript">alert("<This is compatible with XHTML>");</script> +--EXPECT-- +<script type="text/javascript"><!--//--><![CDATA[//><!-- +alert("<This is compatible with XHTML>"); +//--><!]]></script> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-cdata.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-cdata.htmlt new file mode 100644 index 000000000..e7d6d7f5b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-cdata.htmlt @@ -0,0 +1,11 @@ +--INI-- +HTML.Trusted = true +--HTML-- +<script type="text/javascript"><![CDATA[ +alert("<This is compatible with XHTML>"); +]]></script> +--EXPECT-- +<script type="text/javascript"><!--//--><![CDATA[//><!-- +alert("<This is compatible with XHTML>"); +//--><!]]></script> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-comment.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-comment.htmlt new file mode 100644 index 000000000..006901919 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-comment.htmlt @@ -0,0 +1,11 @@ +--INI-- +HTML.Trusted = true +--HTML-- +<script type="text/javascript"><!-- +alert("<This is compatible with XHTML>"); +//--></script> +--EXPECT-- +<script type="text/javascript"><!--//--><![CDATA[//><!-- +alert("<This is compatible with XHTML>"); +//--><!]]></script> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-dbl-comment.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-dbl-comment.htmlt new file mode 100644 index 000000000..ef899b597 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-dbl-comment.htmlt @@ -0,0 +1,11 @@ +--INI-- +HTML.Trusted = true +--HTML-- +<script type="text/javascript"><![CDATA[ +alert("<This is compatible with XHTML>"); +//]]></script> +--EXPECT-- +<script type="text/javascript"><!--//--><![CDATA[//><!-- +alert("<This is compatible with XHTML>"); +//--><!]]></script> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-ideal.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-ideal.htmlt new file mode 100644 index 000000000..70510687c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/script-ideal.htmlt @@ -0,0 +1,11 @@ +--INI-- +HTML.Trusted = true +--HTML-- +<script type="text/javascript"><!--//--><![CDATA[//><!-- +alert("<This is compatible with XHTML>"); +//--><!]]></script> +--EXPECT-- +<script type="text/javascript"><!--//--><![CDATA[//><!-- +alert("<This is compatible with XHTML>"); +//--><!]]></script> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/secure-munge.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/secure-munge.htmlt new file mode 100644 index 000000000..114cb0000 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/secure-munge.htmlt @@ -0,0 +1,10 @@ +--INI-- +URI.Munge = "/redirect.php?url=%s&check=%t" +URI.MungeSecretKey = "foo" +--HTML-- +<a href="http://localhost">foo</a> +<img src="http://localhost" alt="local" /> +--EXPECT-- +<a href="/redirect.php?url=http%3A%2F%2Flocalhost&check=8e8223ae8fac24561104180ea549c21fbd111be7">foo</a> +<img src="http://localhost" alt="local" /> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-preserve-yen.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-preserve-yen.htmlt new file mode 100644 index 000000000..f22417c01 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-preserve-yen.htmlt @@ -0,0 +1,8 @@ +--SKIPIF-- +if (!function_exists('iconv')) return true; +--INI-- +Core.Encoding = "Shift_JIS" +Core.EscapeNonASCIICharacters = true +--HTML-- +<b style="font-family:'¥';">111</b> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-remove-yen.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-remove-yen.htmlt new file mode 100644 index 000000000..6c2d3bc4c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/shift-jis-remove-yen.htmlt @@ -0,0 +1,9 @@ +--SKIPIF-- +if (!function_exists('iconv')) return true; +--INI-- +Core.Encoding = Shift_JIS +--HTML-- +<b style="font-family:'¥';">111</b> +--EXPECT-- +<b style="font-family:'';">111</b> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote-with-inline.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote-with-inline.htmlt new file mode 100644 index 000000000..a6eb0ec66 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote-with-inline.htmlt @@ -0,0 +1,7 @@ +--INI-- +HTML.Doctype = "XHTML 1.0 Strict" +--HTML-- +<blockquote>Illegal <b>contents</b></blockquote> +--EXPECT-- +<blockquote><p>Illegal <b>contents</b></p></blockquote> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote.htmlt new file mode 100644 index 000000000..b61e8dc07 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-blockquote.htmlt @@ -0,0 +1,7 @@ +--INI-- +HTML.Strict = true +--HTML-- +<blockquote>Illegal contents</blockquote> +--EXPECT-- +<blockquote><p>Illegal contents</p></blockquote> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-underline.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-underline.htmlt new file mode 100644 index 000000000..a2e18b1c9 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/strict-underline.htmlt @@ -0,0 +1,7 @@ +--INI-- +HTML.Strict = true +--HTML-- +<u>Illegal underline</u> +--EXPECT-- +<span style="text-decoration:underline;">Illegal underline</span> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/tidy-background.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/tidy-background.htmlt new file mode 100644 index 000000000..08bda2678 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/tidy-background.htmlt @@ -0,0 +1,5 @@ +--HTML-- +<table background="logo.png"><tr><td>asdf</td></tr></table> +--EXPECT-- +<table style="background-image:url("logo.png");"><tr><td>asdf</td></tr></table> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-required.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-required.htmlt new file mode 100644 index 000000000..62dc159d0 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-required.htmlt @@ -0,0 +1,6 @@ +--INI-- +HTML.Trusted = true +--HTML-- +<ul><!-- Foo --></ul> +--EXPECT-- +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-table.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-table.htmlt new file mode 100644 index 000000000..89d80febf --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments-table.htmlt @@ -0,0 +1,5 @@ +--INI-- +HTML.Trusted = true +--HTML-- +<table><!-- foo --><tr><td>Foo</td></tr></table> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments.htmlt new file mode 100644 index 000000000..93b4a35a6 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/trusted-comments.htmlt @@ -0,0 +1,5 @@ +--INI-- +HTML.Trusted = true +--HTML-- +<!-- Foobar --> +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/whitespace-preserve.htmlt b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/whitespace-preserve.htmlt new file mode 100644 index 000000000..861bddaf4 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/HTMLT/whitespace-preserve.htmlt @@ -0,0 +1,3 @@ +--HTML-- +Foo<b> </b>bar +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Harness.php b/lib/htmlpurifier/tests/HTMLPurifier/Harness.php new file mode 100644 index 000000000..d6490a117 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Harness.php @@ -0,0 +1,90 @@ +<?php + +/** + * All-use harness, use this rather than SimpleTest's + */ +class HTMLPurifier_Harness extends UnitTestCase +{ + + public function __construct($name = null) { + parent::__construct($name); + } + + protected $config, $context, $purifier; + + /** + * Generates easily accessible default config/context, as well as + * a convenience purifier for integration testing. + */ + public function setUp() { + list($this->config, $this->context) = $this->createCommon(); + $this->config->set('Output.Newline', ' +'); + $this->purifier = new HTMLPurifier(); + } + + /** + * Asserts a purification. Good for integration testing. + */ + function assertPurification($input, $expect = null) { + if ($expect === null) $expect = $input; + $result = $this->purifier->purify($input, $this->config); + $this->assertIdentical($expect, $result); + } + + + /** + * Accepts config and context and prepares them into a valid state + * @param &$config Reference to config variable + * @param &$context Reference to context variable + */ + protected function prepareCommon(&$config, &$context) { + $config = HTMLPurifier_Config::create($config); + if (!$context) $context = new HTMLPurifier_Context(); + } + + /** + * Generates default configuration and context objects + * @return Defaults in form of array($config, $context) + */ + protected function createCommon() { + return array(HTMLPurifier_Config::createDefault(), new HTMLPurifier_Context); + } + + /** + * Normalizes a string to Unix (\n) endings + */ + protected function normalize(&$string) { + $string = str_replace(array("\r\n", "\r"), "\n", $string); + } + + /** + * If $expect is false, ignore $result and check if status failed. + * Otherwise, check if $status if true and $result === $expect. + * @param $status Boolean status + * @param $result Mixed result from processing + * @param $expect Mixed expectation for result + */ + protected function assertEitherFailOrIdentical($status, $result, $expect) { + if ($expect === false) { + $this->assertFalse($status, 'Expected false result, got true'); + } else { + $this->assertTrue($status, 'Expected true result, got false'); + $this->assertIdentical($result, $expect); + } + } + + public function getTests() { + // __onlytest makes only one test get triggered + foreach (get_class_methods(get_class($this)) as $method) { + if (strtolower(substr($method, 0, 10)) == '__onlytest') { + $this->reporter->paintSkip('All test methods besides ' . $method); + return array($method); + } + } + return parent::getTests(); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/IDAccumulatorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/IDAccumulatorTest.php new file mode 100644 index 000000000..90bca0739 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/IDAccumulatorTest.php @@ -0,0 +1,39 @@ +<?php + +class HTMLPurifier_IDAccumulatorTest extends HTMLPurifier_Harness +{ + + function test() { + + // initialize the accumulator + $accumulator = new HTMLPurifier_IDAccumulator(); + + $this->assertTrue( $accumulator->add('id1')); + $this->assertTrue( $accumulator->add('id2')); + $this->assertFalse($accumulator->add('id1')); // repeated id + + // you can also access the properties (they're public) + $this->assertTrue( isset($accumulator->ids['id2']) ); + + } + + function testLoad() { + + $accumulator = new HTMLPurifier_IDAccumulator(); + + $accumulator->load(array('id1', 'id2', 'id3')); + + $this->assertFalse($accumulator->add('id1')); // repeated id + $this->assertTrue($accumulator->add('id4')); + + } + + function testBuild() { + $this->config->set('Attr.IDBlacklist', array('foo')); + $accumulator = HTMLPurifier_IDAccumulator::build($this->config, $this->context); + $this->assertTrue( isset($accumulator->ids['foo']) ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/AutoParagraphTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/AutoParagraphTest.php new file mode 100644 index 000000000..e6abbd1ff --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/AutoParagraphTest.php @@ -0,0 +1,515 @@ +<?php + +class HTMLPurifier_Injector_AutoParagraphTest extends HTMLPurifier_InjectorHarness +{ + + function setup() { + parent::setup(); + $this->config->set('AutoFormat.AutoParagraph', true); + } + + function testSingleParagraph() { + $this->assertResult( + 'Foobar', + '<p>Foobar</p>' + ); + } + + function testSingleMultiLineParagraph() { + $this->assertResult( +'Par 1 +Par 1 still', +'<p>Par 1 +Par 1 still</p>' + ); + } + + function testTwoParagraphs() { + $this->assertResult( +'Par1 + +Par2', +"<p>Par1</p> + +<p>Par2</p>" + ); + } + + function testTwoParagraphsWithLotsOfSpace() { + $this->assertResult( +'Par1 + + + +Par2', +'<p>Par1</p> + +<p>Par2</p>' + ); + } + + function testTwoParagraphsWithInlineElements() { + $this->assertResult( +'<b>Par1</b> + +<i>Par2</i>', +'<p><b>Par1</b></p> + +<p><i>Par2</i></p>' + ); + } + + function testSingleParagraphThatLooksLikeTwo() { + $this->assertResult( +'<b>Par1 + +Par2</b>', +'<p><b>Par1 + +Par2</b></p>' + ); + } + + function testAddParagraphAdjacentToParagraph() { + $this->assertResult( + 'Par1<p>Par2</p>', +'<p>Par1</p> + +<p>Par2</p>' + ); + } + + function testParagraphUnclosedInlineElement() { + $this->assertResult( + '<b>Par1', + '<p><b>Par1</b></p>' + ); + } + + function testPreservePreTags() { + $this->assertResult( +'<pre>Par1 + +Par1</pre>' + ); + } + + function testIgnoreTrailingWhitespace() { + $this->assertResult( +'Par1 + + ', +'<p>Par1</p> + +' + ); + } + + function testDoNotParagraphBlockElements() { + $this->assertResult( +'Par1 + +<div>Par2</div> + +Par3', +'<p>Par1</p> + +<div>Par2</div> + +<p>Par3</p>' + ); + } + + function testParagraphTextAndInlineNodes() { + $this->assertResult( +'Par<b>1</b>', + '<p>Par<b>1</b></p>' + ); + } + + function testPreserveLeadingWhitespace() { + $this->assertResult( +' + +Par', +' + +<p>Par</p>' + ); + } + + function testPreserveSurroundingWhitespace() { + $this->assertResult( +' + +Par + +', +' + +<p>Par</p> + +' + ); + } + + function testParagraphInsideBlockNode() { + $this->assertResult( +'<div>Par1 + +Par2</div>', +'<div><p>Par1</p> + +<p>Par2</p></div>' + ); + } + + function testParagraphInlineNodeInsideBlockNode() { + $this->assertResult( +'<div><b>Par1</b> + +Par2</div>', +'<div><p><b>Par1</b></p> + +<p>Par2</p></div>' + ); + } + + function testNoParagraphWhenOnlyOneInsideBlockNode() { + $this->assertResult('<div>Par1</div>'); + } + + function testParagraphTwoInlineNodesInsideBlockNode() { + $this->assertResult( +'<div><b>Par1</b> + +<i>Par2</i></div>', +'<div><p><b>Par1</b></p> + +<p><i>Par2</i></p></div>' + ); + } + + function testPreserveInlineNodesInPreTag() { + $this->assertResult( +'<pre><b>Par1</b> + +<i>Par2</i></pre>' + ); + } + + function testSplitUpInternalsOfPTagInBlockNode() { + $this->assertResult( +'<div><p>Foo + +Bar</p></div>', +'<div><p>Foo</p> + +<p>Bar</p></div>' + ); + } + + function testSplitUpInlineNodesInPTagInBlockNode() { + $this->assertResult( +'<div><p><b>Foo</b> + +<i>Bar</i></p></div>', +'<div><p><b>Foo</b></p> + +<p><i>Bar</i></p></div>' + ); + } + + function testNoParagraphSingleInlineNodeInBlockNode() { + $this->assertResult( '<div><b>Foo</b></div>' ); + } + + function testParagraphInBlockquote() { + $this->assertResult( +'<blockquote>Par1 + +Par2</blockquote>', +'<blockquote><p>Par1</p> + +<p>Par2</p></blockquote>' + ); + } + + function testNoParagraphBetweenListItem() { + $this->assertResult( +'<ul><li>Foo</li> + +<li>Bar</li></ul>' + ); + } + + function testParagraphSingleElementWithSurroundingSpace() { + $this->assertResult( +'<div> + +Bar + +</div>', + '<div> + +<p>Bar</p> + +</div>' + ); + } + + function testIgnoreExtraSpaceWithLeadingInlineNode() { + $this->assertResult( +'<b>Par1</b>a + + + +Par2', +'<p><b>Par1</b>a</p> + +<p>Par2</p>' + ); + } + + function testAbsorbExtraEndingPTag() { + $this->assertResult( +'Par1 + +Par2</p>', +'<p>Par1</p> + +<p>Par2</p>' + ); + } + + function testAbsorbExtraEndingDivTag() { + $this->assertResult( +'Par1 + +Par2</div>', +'<p>Par1</p> + +<p>Par2</p>' + ); + } + + function testDoNotParagraphSingleSurroundingSpaceInBlockNode() { + $this->assertResult( +'<div> +Par1 +</div>' + ); + } + + function testBlockNodeTextDelimeterInBlockNode() { + $this->assertResult( +'<div>Par1 + +<div>Par2</div></div>', +'<div><p>Par1</p> + +<div>Par2</div></div>' + ); + } + + function testBlockNodeTextDelimeterWithoutDoublespaceInBlockNode() { + $this->assertResult( +'<div>Par1 +<div>Par2</div></div>' + ); + } + + function testBlockNodeTextDelimeterWithoutDoublespace() { + $this->assertResult( +'Par1 +<div>Par2</div>', +'<p>Par1 +</p> + +<div>Par2</div>' + ); + } + + function testTwoParagraphsOfTextAndInlineNode() { + $this->assertResult( +'Par1 + +<b>Par2</b>', +'<p>Par1</p> + +<p><b>Par2</b></p>' + ); + } + + function testLeadingInlineNodeParagraph() { + $this->assertResult( +'<img /> Foo', +'<p><img /> Foo</p>' + ); + } + + function testTrailingInlineNodeParagraph() { + $this->assertResult( +'<li>Foo <a>bar</a></li>' + ); + } + + function testTwoInlineNodeParagraph() { + $this->assertResult( +'<li><b>baz</b><a>bar</a></li>' + ); + } + + function testNoParagraphTrailingBlockNodeInBlockNode() { + $this->assertResult( +'<div><div>asdf</div><b>asdf</b></div>' + ); + } + + function testParagraphTrailingBlockNodeWithDoublespaceInBlockNode() { + $this->assertResult( +'<div><div>asdf</div> + +<b>asdf</b></div>', +'<div><div>asdf</div> + +<p><b>asdf</b></p></div>' + ); + } + + function testParagraphTwoInlineNodesAndWhitespaceNode() { + $this->assertResult( +'<b>One</b> <i>Two</i>', +'<p><b>One</b> <i>Two</i></p>' + ); + } + + function testNoParagraphWithInlineRootNode() { + $this->config->set('HTML.Parent', 'span'); + $this->assertResult( +'Par + +Par2' + ); + } + + function testInlineAndBlockTagInDivNoParagraph() { + $this->assertResult( + '<div><code>bar</code> mmm <pre>asdf</pre></div>' + ); + } + + function testInlineAndBlockTagInDivNeedingParagraph() { + $this->assertResult( +'<div><code>bar</code> mmm + +<pre>asdf</pre></div>', +'<div><p><code>bar</code> mmm</p> + +<pre>asdf</pre></div>' + ); + } + + function testTextInlineNodeTextThenDoubleNewlineNeedsParagraph() { + $this->assertResult( +'<div>asdf <code>bar</code> mmm + +<pre>asdf</pre></div>', +'<div><p>asdf <code>bar</code> mmm</p> + +<pre>asdf</pre></div>' + ); + } + + function testUpcomingTokenHasNewline() { + $this->assertResult( +'<div>Test<b>foo</b>bar<b>bing</b>bang + +boo</div>', +'<div><p>Test<b>foo</b>bar<b>bing</b>bang</p> + +<p>boo</p></div>' +); + } + + function testEmptyTokenAtEndOfDiv() { + $this->assertResult( +'<div><p>foo</p> +</div>', +'<div><p>foo</p> +</div>' +); + } + + function testEmptyDoubleLineTokenAtEndOfDiv() { + $this->assertResult( +'<div><p>foo</p> + +</div>', +'<div><p>foo</p> + +</div>' +); + } + + function testTextState11Root() { + $this->assertResult('<div></div> '); + } + + function testTextState11Element() { + $this->assertResult( +"<div><div></div> + +</div>"); + } + + function testTextStateLikeElementState111NoWhitespace() { + $this->assertResult('<div><p>P</p>Boo</div>', '<div><p>P</p>Boo</div>'); + } + + function testElementState111NoWhitespace() { + $this->assertResult('<div><p>P</p><b>Boo</b></div>', '<div><p>P</p><b>Boo</b></div>'); + } + + function testElementState133() { + $this->assertResult( +"<div><b>B</b><pre>Ba</pre> + +Bar</div>", +"<div><b>B</b><pre>Ba</pre> + +<p>Bar</p></div>" +); + } + + function testElementState22() { + $this->assertResult( + '<ul><li>foo</li></ul>' + ); + } + + function testElementState311() { + $this->assertResult( + '<p>Foo</p><b>Bar</b>', +'<p>Foo</p> + +<p><b>Bar</b></p>' + ); + } + + function testAutoClose() { + $this->assertResult( + '<p></p> +<hr />' + ); + } + + function testErrorNeeded() { + $this->config->set('HTML.Allowed', 'b'); + $this->expectError('Cannot enable AutoParagraph injector because p is not allowed'); + $this->assertResult('<b>foobar</b>'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/DisplayLinkURITest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/DisplayLinkURITest.php new file mode 100644 index 000000000..1629d9bd1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/DisplayLinkURITest.php @@ -0,0 +1,33 @@ +<?php + +class HTMLPurifier_Injector_DisplayLinkURITest extends HTMLPurifier_InjectorHarness +{ + + function setup() { + parent::setup(); + $this->config->set('AutoFormat.DisplayLinkURI', true); + } + + function testBasicLink() { + $this->assertResult( + '<a href="http://malware.example.com">Don\'t go here!</a>', + '<a>Don\'t go here!</a> (http://malware.example.com)' + ); + } + + function testEmptyLink() { + $this->assertResult( + '<a>Don\'t go here!</a>', + '<a>Don\'t go here!</a>' + ); + } + function testEmptyText() { + $this->assertResult( + '<a href="http://malware.example.com"></a>', + '<a></a> (http://malware.example.com)' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/LinkifyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/LinkifyTest.php new file mode 100644 index 000000000..1a1542d53 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/LinkifyTest.php @@ -0,0 +1,50 @@ +<?php + +class HTMLPurifier_Injector_LinkifyTest extends HTMLPurifier_InjectorHarness +{ + + function setup() { + parent::setup(); + $this->config->set('AutoFormat.Linkify', true); + } + + function testLinkifyURLInRootNode() { + $this->assertResult( + 'http://example.com', + '<a href="http://example.com">http://example.com</a>' + ); + } + + function testLinkifyURLInInlineNode() { + $this->assertResult( + '<b>http://example.com</b>', + '<b><a href="http://example.com">http://example.com</a></b>' + ); + } + + function testBasicUsageCase() { + $this->assertResult( + 'This URL http://example.com is what you need', + 'This URL <a href="http://example.com">http://example.com</a> is what you need' + ); + } + + function testIgnoreURLInATag() { + $this->assertResult( + '<a>http://example.com/</a>' + ); + } + + function testNeeded() { + $this->config->set('HTML.Allowed', 'b'); + $this->expectError('Cannot enable Linkify injector because a is not allowed'); + $this->assertResult('http://example.com/'); + } + + function testExcludes() { + $this->assertResult('<a><span>http://example.com</span></a>'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/PurifierLinkifyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/PurifierLinkifyTest.php new file mode 100644 index 000000000..774b234ad --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/PurifierLinkifyTest.php @@ -0,0 +1,59 @@ +<?php + +class HTMLPurifier_Injector_PurifierLinkifyTest extends HTMLPurifier_InjectorHarness +{ + + function setup() { + parent::setup(); + $this->config->set('AutoFormat.PurifierLinkify', true); + $this->config->set('AutoFormat.PurifierLinkify.DocURL', '#%s'); + } + + function testNoTriggerCharacer() { + $this->assertResult('Foobar'); + } + + function testTriggerCharacterInIrrelevantContext() { + $this->assertResult('20% off!'); + } + + function testPreserveNamespace() { + $this->assertResult('%Core namespace (not recognized)'); + } + + function testLinkifyBasic() { + $this->assertResult( + '%Namespace.Directive', + '<a href="#Namespace.Directive">%Namespace.Directive</a>' + ); + } + + function testLinkifyWithAdjacentTextNodes() { + $this->assertResult( + 'This %Namespace.Directive thing', + 'This <a href="#Namespace.Directive">%Namespace.Directive</a> thing' + ); + } + + function testLinkifyInBlock() { + $this->assertResult( + '<div>This %Namespace.Directive thing</div>', + '<div>This <a href="#Namespace.Directive">%Namespace.Directive</a> thing</div>' + ); + } + + function testPreserveInATag() { + $this->assertResult( + '<a>%Namespace.Directive</a>' + ); + } + + function testNeeded() { + $this->config->set('HTML.Allowed', 'b'); + $this->expectError('Cannot enable PurifierLinkify injector because a is not allowed'); + $this->assertResult('%Namespace.Directive'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveEmptyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveEmptyTest.php new file mode 100644 index 000000000..34dbc9515 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveEmptyTest.php @@ -0,0 +1,80 @@ +<?php + +class HTMLPurifier_Injector_RemoveEmptyTest extends HTMLPurifier_InjectorHarness +{ + + public function setup() { + parent::setup(); + $this->config->set('AutoFormat.RemoveEmpty', true); + } + + function testPreserve() { + $this->assertResult('<b>asdf</b>'); + } + + function testRemove() { + $this->assertResult('<b></b>', ''); + } + + function testRemoveWithSpace() { + $this->assertResult('<b> </b>', ''); + } + + function testRemoveWithAttr() { + $this->assertResult('<b class="asdf"></b>', ''); + } + + function testRemoveIdAndName() { + $this->assertResult('<a id="asdf" name="asdf"></a>', ''); + } + + function testPreserveColgroup() { + $this->assertResult('<colgroup></colgroup>'); + } + + function testPreserveId() { + $this->config->set('Attr.EnableID', true); + $this->assertResult('<a id="asdf"></a>'); + } + + function testPreserveName() { + $this->config->set('Attr.EnableID', true); + $this->assertResult('<a name="asdf"></a>'); + } + + function testRemoveNested() { + $this->assertResult('<b><i></i></b>', ''); + } + + function testRemoveNested2() { + $this->assertResult('<b><i><u></u></i></b>', ''); + } + + function testRemoveNested3() { + $this->assertResult('<b> <i> <u> </u> </i> </b>', ''); + } + + function testRemoveNbsp() { + $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true); + $this->assertResult('<b> </b>', ''); + } + + function testRemoveNbspMix() { + $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true); + $this->assertResult('<b> </b>', ''); + } + + function testDontRemoveNbsp() { + $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true); + $this->assertResult('<td> </b>', "<td>\xC2\xA0</td>"); + } + + function testRemoveNbspExceptionsSpecial() { + $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true); + $this->config->set('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions', 'b'); + $this->assertResult('<b> </b>', "<b>\xC2\xA0</b>"); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveSpansWithoutAttributesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveSpansWithoutAttributesTest.php new file mode 100755 index 000000000..704c1cd7b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/RemoveSpansWithoutAttributesTest.php @@ -0,0 +1,99 @@ +<?php + +class HTMLPurifier_Injector_RemoveSpansWithoutAttributesTest extends HTMLPurifier_InjectorHarness +{ + function setup() { + parent::setup(); + $this->config->set('HTML.Allowed', 'span[class],div,p,strong,em'); + $this->config->set('AutoFormat.RemoveSpansWithoutAttributes', true); + } + + function testSingleSpan() { + $this->assertResult( + '<span>foo</span>', + 'foo' + ); + } + + function testSingleSpanWithAttributes() { + $this->assertResult( + '<span class="bar">foo</span>', + '<span class="bar">foo</span>' + ); + } + + function testSingleNestedSpan() { + $this->assertResult( + '<p><span>foo</span></p>', + '<p>foo</p>' + ); + } + + function testSingleNestedSpanWithAttributes() { + $this->assertResult( + '<p><span class="bar">foo</span></p>', + '<p><span class="bar">foo</span></p>' + ); + } + + + function testSpanWithChildren() { + $this->assertResult( + '<span>foo <strong>bar</strong> <em>baz</em></span>', + 'foo <strong>bar</strong> <em>baz</em>' + ); + } + + function testSpanWithSiblings() { + $this->assertResult( + '<p>before <span>inside</span> <strong>after</strong></p>', + '<p>before inside <strong>after</strong></p>' + ); + } + + function testNestedSpanWithSiblingsAndChildren() { + $this->assertResult( + '<p>a <span>b <em>c</em> d</span> e</p>', + '<p>a b <em>c</em> d e</p>' + ); + } + + function testNestedSpansWithoutAttributes() { + $this->assertResult( + '<span>one<span>two<span>three</span></span></span>', + 'onetwothree' + ); + } + + function testDeeplyNestedSpan() { + $this->assertResult( + '<div><div><div><span class="a">a <span>b</span> c</span></div></div></div>', + '<div><div><div><span class="a">a b c</span></div></div></div>' + ); + } + + function testSpanWithInvalidAttributes() { + $this->assertResult( + '<p><span snorkel buzzer="emu">foo</span></p>', + '<p>foo</p>' + ); + } + + function testNestedAlternateSpans() { + $this->assertResult( +'<span>a <span class="x">b <span>c <span class="y">d <span>e <span class="z">f +</span></span></span></span></span></span>', +'a <span class="x">b c <span class="y">d e <span class="z">f +</span></span></span>' + ); + } + + function testSpanWithSomeInvalidAttributes() { + $this->assertResult( + '<p><span buzzer="emu" class="bar">foo</span></p>', + '<p><span class="bar">foo</span></p>' + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Injector/SafeObjectTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Injector/SafeObjectTest.php new file mode 100644 index 000000000..59e0493c3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Injector/SafeObjectTest.php @@ -0,0 +1,88 @@ +<?php + +/** + * This test is kinda weird, because it doesn't test the full safe object + * functionality, just a small section of it. Or maybe it's actually the right + * way. + */ +class HTMLPurifier_Injector_SafeObjectTest extends HTMLPurifier_InjectorHarness +{ + + function setup() { + parent::setup(); + // there is no AutoFormat.SafeObject directive + $this->config->set('AutoFormat.Custom', array(new HTMLPurifier_Injector_SafeObject())); + $this->config->set('HTML.Trusted', true); + } + + function testPreserve() { + $this->assertResult( + '<b>asdf</b>' + ); + } + + function testRemoveStrayParam() { + $this->assertResult( + '<param />', + '' + ); + } + + function testEditObjectParam() { + $this->assertResult( + '<object></object>', + '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>' + ); + } + + function testIgnoreStrayParam() { + $this->assertResult( + '<object><param /></object>', + '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>' + ); + } + + function testIgnoreDuplicates() { + $this->assertResult( + '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>' + ); + } + + function testIgnoreBogusData() { + $this->assertResult( + '<object><param name="allowScriptAccess" value="always" /><param name="allowNetworking" value="always" /></object>', + '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>' + ); + } + + function testIgnoreInvalidData() { + $this->assertResult( + '<object><param name="foo" value="bar" /></object>', + '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object>' + ); + } + + function testKeepValidData() { + $this->assertResult( + '<object><param name="movie" value="bar" /></object>', + '<object data="bar"><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><param name="movie" value="bar" /></object>' + ); + } + + function testNested() { + $this->assertResult( + '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><object></object></object>', + '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></object></object>' + ); + } + + function testNotActuallyNested() { + $this->assertResult( + '<object><p><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /></p></object>', + '<object><param name="allowScriptAccess" value="never" /><param name="allowNetworking" value="internal" /><p></p></object>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/InjectorHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/InjectorHarness.php new file mode 100644 index 000000000..e3815175a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/InjectorHarness.php @@ -0,0 +1,13 @@ +<?php + +class HTMLPurifier_InjectorHarness extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_MakeWellFormed(); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/LanguageFactoryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/LanguageFactoryTest.php new file mode 100644 index 000000000..9fea24f42 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/LanguageFactoryTest.php @@ -0,0 +1,70 @@ +<?php + +class HTMLPurifier_LanguageFactoryTest extends HTMLPurifier_Harness +{ + + /** + * Protected reference of global factory we're testing. + */ + protected $factory; + + public function setUp() { + $this->factory = HTMLPurifier_LanguageFactory::instance(); + parent::setUp(); + } + + function test() { + + $this->config->set('Core.Language', 'en'); + $language = $this->factory->create($this->config, $this->context); + + $this->assertIsA($language, 'HTMLPurifier_Language'); + $this->assertIdentical($language->code, 'en'); + + // lazy loading test + $this->assertIdentical(count($language->messages), 0); + $language->load(); + $this->assertNotEqual(count($language->messages), 0); + + } + + function testFallback() { + + $this->config->set('Core.Language', 'en-x-test'); + $language = $this->factory->create($this->config, $this->context); + + $this->assertIsA($language, 'HTMLPurifier_Language_en_x_test'); + $this->assertIdentical($language->code, 'en-x-test'); + + $language->load(); + + // test overloaded message + $this->assertIdentical($language->getMessage('HTMLPurifier'), 'HTML Purifier X'); + + // test inherited message + $this->assertIdentical($language->getMessage('LanguageFactoryTest: Pizza'), 'Pizza'); + + } + + function testFallbackWithNoClass() { + $this->config->set('Core.Language', 'en-x-testmini'); + $language = $this->factory->create($this->config, $this->context); + $this->assertIsA($language, 'HTMLPurifier_Language'); + $this->assertIdentical($language->code, 'en-x-testmini'); + $language->load(); + $this->assertIdentical($language->getMessage('HTMLPurifier'), 'HTML Purifier XNone'); + $this->assertIdentical($language->getMessage('LanguageFactoryTest: Pizza'), 'Pizza'); + $this->assertIdentical($language->error, false); + } + + function testNoSuchLanguage() { + $this->config->set('Core.Language', 'en-x-testnone'); + $language = $this->factory->create($this->config, $this->context); + $this->assertIsA($language, 'HTMLPurifier_Language'); + $this->assertIdentical($language->code, 'en-x-testnone'); + $this->assertIdentical($language->error, true); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/LanguageTest.php b/lib/htmlpurifier/tests/HTMLPurifier/LanguageTest.php new file mode 100644 index 000000000..033b6aa71 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/LanguageTest.php @@ -0,0 +1,79 @@ +<?php + +/** + * @todo Fix usage of HTMLPurifier_Language->_loaded using something else + */ +class HTMLPurifier_LanguageTest extends HTMLPurifier_Harness +{ + + protected $lang; + + protected function generateEnLanguage() { + $factory = HTMLPurifier_LanguageFactory::instance(); + $config = HTMLPurifier_Config::create(array('Core.Language' => 'en')); + $context = new HTMLPurifier_Context(); + return $factory->create($config, $context); + } + + function test_getMessage() { + $config = HTMLPurifier_Config::createDefault(); + $context = new HTMLPurifier_Context(); + $lang = new HTMLPurifier_Language($config, $context); + $lang->_loaded = true; + $lang->messages['HTMLPurifier'] = 'HTML Purifier'; + $this->assertIdentical($lang->getMessage('HTMLPurifier'), 'HTML Purifier'); + $this->assertIdentical($lang->getMessage('LanguageTest: Totally non-existent key'), '[LanguageTest: Totally non-existent key]'); + } + + function test_formatMessage() { + $config = HTMLPurifier_Config::createDefault(); + $context = new HTMLPurifier_Context(); + $lang = new HTMLPurifier_Language($config, $context); + $lang->_loaded = true; + $lang->messages['LanguageTest: Error'] = 'Error is $1 on line $2'; + $this->assertIdentical($lang->formatMessage('LanguageTest: Error', array(1=>'fatal', 32)), 'Error is fatal on line 32'); + } + + function test_formatMessage_tokenParameter() { + $config = HTMLPurifier_Config::createDefault(); + $context = new HTMLPurifier_Context(); + $generator = new HTMLPurifier_Generator($config, $context); // replace with mock if this gets icky + $context->register('Generator', $generator); + $lang = new HTMLPurifier_Language($config, $context); + $lang->_loaded = true; + $lang->messages['LanguageTest: Element info'] = 'Element Token: $1.Name, $1.Serialized, $1.Compact, $1.Line'; + $lang->messages['LanguageTest: Data info'] = 'Data Token: $1.Data, $1.Serialized, $1.Compact, $1.Line'; + $this->assertIdentical($lang->formatMessage('LanguageTest: Element info', + array(1=>new HTMLPurifier_Token_Start('a', array('href'=>'http://example.com'), 18))), + 'Element Token: a, <a href="http://example.com">, <a>, 18'); + $this->assertIdentical($lang->formatMessage('LanguageTest: Data info', + array(1=>new HTMLPurifier_Token_Text('data>', 23))), + 'Data Token: data>, data>, data>, 23'); + } + + function test_listify() { + $lang = $this->generateEnLanguage(); + $this->assertEqual($lang->listify(array('Item')), 'Item'); + $this->assertEqual($lang->listify(array('Item', 'Item2')), 'Item and Item2'); + $this->assertEqual($lang->listify(array('Item', 'Item2', 'Item3')), 'Item, Item2 and Item3'); + } + + function test_formatMessage_arrayParameter() { + $lang = $this->generateEnLanguage(); + + $array = array('Item1', 'Item2', 'Item3'); + $this->assertIdentical( + $lang->formatMessage('LanguageTest: List', array(1=>$array)), + 'Item1, Item2 and Item3' + ); + + $array = array('Key1' => 'Value1', 'Key2' => 'Value2'); + $this->assertIdentical( + $lang->formatMessage('LanguageTest: Hash', array(1=>$array)), + 'Key1 and Key2; Value1 and Value2' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/LengthTest.php b/lib/htmlpurifier/tests/HTMLPurifier/LengthTest.php new file mode 100644 index 000000000..2968b6d33 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/LengthTest.php @@ -0,0 +1,73 @@ +<?php + +class HTMLPurifier_LengthTest extends HTMLPurifier_Harness +{ + + function testConstruct() { + $l = new HTMLPurifier_Length('23', 'in'); + $this->assertIdentical($l->getN(), '23'); + $this->assertIdentical($l->getUnit(), 'in'); + } + + function testMake() { + $l = HTMLPurifier_Length::make('+23.4in'); + $this->assertIdentical($l->getN(), '+23.4'); + $this->assertIdentical($l->getUnit(), 'in'); + } + + function testToString() { + $l = new HTMLPurifier_Length('23', 'in'); + $this->assertIdentical($l->toString(), '23in'); + } + + protected function assertValidate($string, $expect = true) { + if ($expect === true) $expect = $string; + $l = HTMLPurifier_Length::make($string); + $result = $l->isValid(); + if ($result === false) $this->assertIdentical($expect, false); + else $this->assertIdentical($l->toString(), $expect); + } + + function testValidate() { + $this->assertValidate('0'); + $this->assertValidate('+0', '0'); + $this->assertValidate('-0', '0'); + $this->assertValidate('0px'); + $this->assertValidate('4.5px'); + $this->assertValidate('-4.5px'); + $this->assertValidate('3ex'); + $this->assertValidate('3em'); + $this->assertValidate('3in'); + $this->assertValidate('3cm'); + $this->assertValidate('3mm'); + $this->assertValidate('3pt'); + $this->assertValidate('3pc'); + $this->assertValidate('3PX', '3px'); + $this->assertValidate('3', false); + $this->assertValidate('3miles', false); + } + + /** + * @param $s1 First string to compare + * @param $s2 Second string to compare + * @param $expect 0 for $s1 == $s2, 1 for $s1 > $s2 and -1 for $s1 < $s2 + */ + protected function assertComparison($s1, $s2, $expect = 0) { + $l1 = HTMLPurifier_Length::make($s1); + $l2 = HTMLPurifier_Length::make($s2); + $r1 = $l1->compareTo($l2); + $r2 = $l2->compareTo($l1); + $this->assertIdentical($r1 == 0 ? 0 : ($r1 > 0 ? 1 : -1), $expect); + $this->assertIdentical($r2 == 0 ? 0 : ($r2 > 0 ? 1 : -1), - $expect); + } + + function testCompareTo() { + $this->assertComparison('12in', '12in'); + $this->assertComparison('12in', '12mm', 1); + $this->assertComparison('1px', '1mm', -1); + $this->assertComparison(str_repeat('2', 38) . 'in', '100px', 1); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLexTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLexTest.php new file mode 100644 index 000000000..095b5c952 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLexTest.php @@ -0,0 +1,130 @@ +<?php + +class HTMLPurifier_Lexer_DirectLexTest extends HTMLPurifier_Harness +{ + + protected $DirectLex; + + function setUp() { + $this->DirectLex = new HTMLPurifier_Lexer_DirectLex(); + } + + // internals testing + function test_parseAttributeString() { + + $input[0] = 'href="about:blank" rel="nofollow"'; + $expect[0] = array('href'=>'about:blank', 'rel'=>'nofollow'); + + $input[1] = "href='about:blank'"; + $expect[1] = array('href'=>'about:blank'); + + // note that the single quotes aren't /really/ escaped + $input[2] = 'onclick="javascript:alert(\'asdf\');"'; + $expect[2] = array('onclick' => "javascript:alert('asdf');"); + + $input[3] = 'selected'; + $expect[3] = array('selected'=>'selected'); + + // [INVALID] + $input[4] = '="nokey"'; + $expect[4] = array(); + + // [SIMPLE] + $input[5] = 'color=blue'; + $expect[5] = array('color' => 'blue'); + + // [INVALID] + $input[6] = 'href="about:blank'; + $expect[6] = array('href' => 'about:blank'); + + // [INVALID] + $input[7] = '"='; + $expect[7] = array('"' => ''); + // we ought to get array() + + $input[8] = 'href ="about:blank"rel ="nofollow"'; + $expect[8] = array('href' => 'about:blank', 'rel' => 'nofollow'); + + $input[9] = 'two bool'; + $expect[9] = array('two' => 'two', 'bool' => 'bool'); + + $input[10] = 'name="input" selected'; + $expect[10] = array('name' => 'input', 'selected' => 'selected'); + + $input[11] = '=""'; + $expect[11] = array(); + + $input[12] = '="" =""'; + $expect[12] = array('"' => ''); // tough to say, just don't throw a loop + + $input[13] = 'href="'; + $expect[13] = array('href' => ''); + + $input[14] = 'href=" <'; + $expect[14] = array('href' => ' <'); + + $config = HTMLPurifier_Config::createDefault(); + $context = new HTMLPurifier_Context(); + $size = count($input); + for($i = 0; $i < $size; $i++) { + $result = $this->DirectLex->parseAttributeString($input[$i], $config, $context); + $this->assertIdentical($expect[$i], $result, 'Test ' . $i . ': %s'); + } + + } + + function testLineNumbers() { + + // . . . . . . . . . . + // 01234567890123 01234567890123 0123456789012345 0123456789012 012345 + $html = "<b>Line 1</b>\n<i>Line 2</i>\nStill Line 2<br\n/>Now Line 4\n\n<br />"; + + $expect = array( + // line 1 + 0 => new HTMLPurifier_Token_Start('b') + ,1 => new HTMLPurifier_Token_Text('Line 1') + ,2 => new HTMLPurifier_Token_End('b') + ,3 => new HTMLPurifier_Token_Text("\n") + // line 2 + ,4 => new HTMLPurifier_Token_Start('i') + ,5 => new HTMLPurifier_Token_Text('Line 2') + ,6 => new HTMLPurifier_Token_End('i') + ,7 => new HTMLPurifier_Token_Text("\nStill Line 2") + // line 3 + ,8 => new HTMLPurifier_Token_Empty('br') + // line 4 + ,9 => new HTMLPurifier_Token_Text("Now Line 4\n\n") + // line SIX + ,10 => new HTMLPurifier_Token_Empty('br') + ); + + $context = new HTMLPurifier_Context(); + $config = HTMLPurifier_Config::createDefault(); + $output = $this->DirectLex->tokenizeHTML($html, $config, $context); + + $this->assertIdentical($output, $expect); + + $context = new HTMLPurifier_Context(); + $config = HTMLPurifier_Config::create(array( + 'Core.MaintainLineNumbers' => true + )); + $expect[0]->position(1, 0); + $expect[1]->position(1, 3); + $expect[2]->position(1, 9); + $expect[3]->position(2, -1); + $expect[4]->position(2, 0); + $expect[5]->position(2, 3); + $expect[6]->position(2, 9); + $expect[7]->position(3, -1); + $expect[8]->position(3, 12); + $expect[9]->position(4, 2); + $expect[10]->position(6, 0); + + $output = $this->DirectLex->tokenizeHTML($html, $config, $context); + $this->assertIdentical($output, $expect); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLex_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLex_ErrorsTest.php new file mode 100644 index 000000000..69d3c628d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Lexer/DirectLex_ErrorsTest.php @@ -0,0 +1,58 @@ +<?php + +class HTMLPurifier_Lexer_DirectLex_ErrorsTest extends HTMLPurifier_ErrorsHarness +{ + + function invoke($input) { + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $lexer->tokenizeHTML($input, $this->config, $this->context); + } + + function invokeAttr($input) { + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $lexer->parseAttributeString($input, $this->config, $this->context); + } + + function testExtractBody() { + $this->expectErrorCollection(E_WARNING, 'Lexer: Extracted body'); + $this->invoke('<body>foo</body>'); + } + + function testUnclosedComment() { + $this->expectErrorCollection(E_WARNING, 'Lexer: Unclosed comment'); + $this->expectContext('CurrentLine', 1); + $this->invoke('<!-- >'); + } + + function testUnescapedLt() { + $this->expectErrorCollection(E_NOTICE, 'Lexer: Unescaped lt'); + $this->expectContext('CurrentLine', 1); + $this->invoke('< foo>'); + } + + function testMissingGt() { + $this->expectErrorCollection(E_WARNING, 'Lexer: Missing gt'); + $this->expectContext('CurrentLine', 1); + $this->invoke('<a href=""'); + } + + // these are sub-errors, will only be thrown in context of collector + + function testMissingAttributeKey1() { + $this->expectErrorCollection(E_ERROR, 'Lexer: Missing attribute key'); + $this->invokeAttr('=""'); + } + + function testMissingAttributeKey2() { + $this->expectErrorCollection(E_ERROR, 'Lexer: Missing attribute key'); + $this->invokeAttr('foo="bar" =""'); + } + + function testMissingEndQuote() { + $this->expectErrorCollection(E_ERROR, 'Lexer: Missing end quote'); + $this->invokeAttr('src="foo'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/LexerTest.php b/lib/htmlpurifier/tests/HTMLPurifier/LexerTest.php new file mode 100644 index 000000000..42a59aeb6 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/LexerTest.php @@ -0,0 +1,752 @@ +<?php + +class HTMLPurifier_LexerTest extends HTMLPurifier_Harness +{ + + protected $_has_pear = false; + + public function __construct() { + parent::__construct(); + if ($GLOBALS['HTMLPurifierTest']['PH5P']) { + require_once 'HTMLPurifier/Lexer/PH5P.php'; + } + } + + // HTMLPurifier_Lexer::create() -------------------------------------------- + + function test_create() { + $this->config->set('Core.MaintainLineNumbers', true); + $lexer = HTMLPurifier_Lexer::create($this->config); + $this->assertIsA($lexer, 'HTMLPurifier_Lexer_DirectLex'); + } + + function test_create_objectLexerImpl() { + $this->config->set('Core.LexerImpl', new HTMLPurifier_Lexer_DirectLex()); + $lexer = HTMLPurifier_Lexer::create($this->config); + $this->assertIsA($lexer, 'HTMLPurifier_Lexer_DirectLex'); + } + + function test_create_unknownLexer() { + $this->config->set('Core.LexerImpl', 'AsdfAsdf'); + $this->expectException(new HTMLPurifier_Exception('Cannot instantiate unrecognized Lexer type AsdfAsdf')); + HTMLPurifier_Lexer::create($this->config); + } + + function test_create_incompatibleLexer() { + $this->config->set('Core.LexerImpl', 'DOMLex'); + $this->config->set('Core.MaintainLineNumbers', true); + $this->expectException(new HTMLPurifier_Exception('Cannot use lexer that does not support line numbers with Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)')); + HTMLPurifier_Lexer::create($this->config); + } + + // HTMLPurifier_Lexer->parseData() ----------------------------------------- + + function assertParseData($input, $expect = true) { + if ($expect === true) $expect = $input; + $lexer = new HTMLPurifier_Lexer(); + $this->assertIdentical($expect, $lexer->parseData($input)); + } + + function test_parseData_plainText() { + $this->assertParseData('asdf'); + } + + function test_parseData_ampersandEntity() { + $this->assertParseData('&', '&'); + } + + function test_parseData_quotEntity() { + $this->assertParseData('"', '"'); + } + + function test_parseData_aposNumericEntity() { + $this->assertParseData(''', "'"); + } + + function test_parseData_aposCompactNumericEntity() { + $this->assertParseData(''', "'"); + } + + function test_parseData_adjacentAmpersandEntities() { + $this->assertParseData('&&&', '&&&'); + } + + function test_parseData_trailingUnescapedAmpersand() { + $this->assertParseData('&&', '&&'); + } + + function test_parseData_internalUnescapedAmpersand() { + $this->assertParseData('Procter & Gamble'); + } + + function test_parseData_improperEntityFaultToleranceTest() { + $this->assertParseData('-'); + } + + // HTMLPurifier_Lexer->extractBody() --------------------------------------- + + function assertExtractBody($text, $extract = true) { + $lexer = new HTMLPurifier_Lexer(); + $result = $lexer->extractBody($text); + if ($extract === true) $extract = $text; + $this->assertIdentical($extract, $result); + } + + function test_extractBody_noBodyTags() { + $this->assertExtractBody('<b>Bold</b>'); + } + + function test_extractBody_lowercaseBodyTags() { + $this->assertExtractBody('<html><body><b>Bold</b></body></html>', '<b>Bold</b>'); + } + + function test_extractBody_uppercaseBodyTags() { + $this->assertExtractBody('<HTML><BODY><B>Bold</B></BODY></HTML>', '<B>Bold</B>'); + } + + function test_extractBody_realisticUseCase() { + $this->assertExtractBody( +'<?xml version="1.0" +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>xyz</title> + </head> + <body> + <form method="post" action="whatever1"> + <div> + <input type="text" name="username" /> + <input type="text" name="password" /> + <input type="submit" /> + </div> + </form> + </body> +</html>', + ' + <form method="post" action="whatever1"> + <div> + <input type="text" name="username" /> + <input type="text" name="password" /> + <input type="submit" /> + </div> + </form> + '); + } + + function test_extractBody_bodyWithAttributes() { + $this->assertExtractBody('<html><body bgcolor="#F00"><b>Bold</b></body></html>', '<b>Bold</b>'); + } + + function test_extractBody_preserveUnclosedBody() { + $this->assertExtractBody('<body>asdf'); // not closed, don't accept + } + + function test_extractBody_useLastBody() { + $this->assertExtractBody('<body>foo</body>bar</body>', 'foo</body>bar'); + } + + // HTMLPurifier_Lexer->tokenizeHTML() -------------------------------------- + + function assertTokenization($input, $expect, $alt_expect = array()) { + $lexers = array(); + $lexers['DirectLex'] = new HTMLPurifier_Lexer_DirectLex(); + if (class_exists('DOMDocument')) { + $lexers['DOMLex'] = new HTMLPurifier_Lexer_DOMLex(); + $lexers['PH5P'] = new HTMLPurifier_Lexer_PH5P(); + } + foreach ($lexers as $name => $lexer) { + $result = $lexer->tokenizeHTML($input, $this->config, $this->context); + if (isset($alt_expect[$name])) { + if ($alt_expect[$name] === false) continue; + $t_expect = $alt_expect[$name]; + $this->assertIdentical($result, $alt_expect[$name], "$name: %s"); + } else { + $t_expect = $expect; + $this->assertIdentical($result, $expect, "$name: %s"); + } + if ($t_expect != $result) { + printTokens($result); + } + } + } + + function test_tokenizeHTML_emptyInput() { + $this->assertTokenization('', array()); + } + + function test_tokenizeHTML_plainText() { + $this->assertTokenization( + 'This is regular text.', + array( + new HTMLPurifier_Token_Text('This is regular text.') + ) + ); + } + + function test_tokenizeHTML_textAndTags() { + $this->assertTokenization( + 'This is <b>bold</b> text', + array( + new HTMLPurifier_Token_Text('This is '), + new HTMLPurifier_Token_Start('b', array()), + new HTMLPurifier_Token_Text('bold'), + new HTMLPurifier_Token_End('b'), + new HTMLPurifier_Token_Text(' text'), + ) + ); + } + + function test_tokenizeHTML_normalizeCase() { + $this->assertTokenization( + '<DIV>Totally rad dude. <b>asdf</b></div>', + array( + new HTMLPurifier_Token_Start('DIV', array()), + new HTMLPurifier_Token_Text('Totally rad dude. '), + new HTMLPurifier_Token_Start('b', array()), + new HTMLPurifier_Token_Text('asdf'), + new HTMLPurifier_Token_End('b'), + new HTMLPurifier_Token_End('div'), + ) + ); + } + + function test_tokenizeHTML_notWellFormed() { + $this->assertTokenization( + '<asdf></asdf><d></d><poOloka><poolasdf><ds></asdf></ASDF>', + array( + new HTMLPurifier_Token_Start('asdf'), + new HTMLPurifier_Token_End('asdf'), + new HTMLPurifier_Token_Start('d'), + new HTMLPurifier_Token_End('d'), + new HTMLPurifier_Token_Start('poOloka'), + new HTMLPurifier_Token_Start('poolasdf'), + new HTMLPurifier_Token_Start('ds'), + new HTMLPurifier_Token_End('asdf'), + new HTMLPurifier_Token_End('ASDF'), + ), + array( + 'DOMLex' => $alt = array( + new HTMLPurifier_Token_Empty('asdf'), + new HTMLPurifier_Token_Empty('d'), + new HTMLPurifier_Token_Start('pooloka'), + new HTMLPurifier_Token_Start('poolasdf'), + new HTMLPurifier_Token_Empty('ds'), + new HTMLPurifier_Token_End('poolasdf'), + new HTMLPurifier_Token_End('pooloka'), + ), + 'PH5P' => $alt, + ) + ); + } + + function test_tokenizeHTML_whitespaceInTag() { + $this->assertTokenization( + '<a'."\t".'href="foobar.php"'."\n".'title="foo!">Link to <b id="asdf">foobar</b></a>', + array( + new HTMLPurifier_Token_Start('a',array('href'=>'foobar.php','title'=>'foo!')), + new HTMLPurifier_Token_Text('Link to '), + new HTMLPurifier_Token_Start('b',array('id'=>'asdf')), + new HTMLPurifier_Token_Text('foobar'), + new HTMLPurifier_Token_End('b'), + new HTMLPurifier_Token_End('a'), + ) + ); + } + + function test_tokenizeHTML_singleAttribute() { + $this->assertTokenization( + '<br style="&" />', + array( + new HTMLPurifier_Token_Empty('br', array('style' => '&')) + ) + ); + } + + function test_tokenizeHTML_emptyTag() { + $this->assertTokenization( + '<br />', + array( new HTMLPurifier_Token_Empty('br') ) + ); + } + + function test_tokenizeHTML_comment() { + $this->assertTokenization( + '<!-- Comment -->', + array( new HTMLPurifier_Token_Comment(' Comment ') ) + ); + } + + function test_tokenizeHTML_malformedComment() { + $this->assertTokenization( + '<!-- not so well formed --->', + array( new HTMLPurifier_Token_Comment(' not so well formed -') ) + ); + } + + function test_tokenizeHTML_unterminatedTag() { + $this->assertTokenization( + '<a href=""', + array( new HTMLPurifier_Token_Text('<a href=""') ), + array( + // I like our behavior better, but it's non-standard + 'DOMLex' => array( new HTMLPurifier_Token_Empty('a', array('href'=>'')) ), + 'PH5P' => false, // total barfing, grabs scaffolding too + ) + ); + } + + function test_tokenizeHTML_specialEntities() { + $this->assertTokenization( + '<b>', + array( + new HTMLPurifier_Token_Text('<b>') + ), + array( + // some parsers will separate entities out + 'PH5P' => array( + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('b'), + new HTMLPurifier_Token_Text('>'), + ), + ) + ); + } + + function test_tokenizeHTML_earlyQuote() { + $this->assertTokenization( + '<a "=>', + array( new HTMLPurifier_Token_Empty('a') ), + array( + // we barf on this input + 'DirectLex' => array( + new HTMLPurifier_Token_Start('a', array('"' => '')) + ), + 'PH5P' => false, // behavior varies; handle this personally + ) + ); + } + + function test_tokenizeHTML_earlyQuote_PH5P() { + if (!class_exists('DOMDocument')) return; + $lexer = new HTMLPurifier_Lexer_PH5P(); + $result = $lexer->tokenizeHTML('<a "=>', $this->config, $this->context); + if ($this->context->get('PH5PError', true)) { + $this->assertIdentical(array( + new HTMLPurifier_Token_Start('a', array('"' => '')) + ), $result); + } else { + $this->assertIdentical(array( + new HTMLPurifier_Token_Empty('a', array('"' => '')) + ), $result); + } + } + + function test_tokenizeHTML_unescapedQuote() { + $this->assertTokenization( + '"', + array( new HTMLPurifier_Token_Text('"') ) + ); + } + + function test_tokenizeHTML_escapedQuote() { + $this->assertTokenization( + '"', + array( new HTMLPurifier_Token_Text('"') ) + ); + } + + function test_tokenizeHTML_cdata() { + $this->assertTokenization( + '<![CDATA[You <b>can't</b> get me!]]>', + array( new HTMLPurifier_Token_Text('You <b>can't</b> get me!') ), + array( + 'PH5P' => array( + new HTMLPurifier_Token_Text('You '), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('b'), + new HTMLPurifier_Token_Text('>'), + new HTMLPurifier_Token_Text('can'), + new HTMLPurifier_Token_Text('&'), + new HTMLPurifier_Token_Text('#39;t'), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('/b'), + new HTMLPurifier_Token_Text('>'), + new HTMLPurifier_Token_Text(' get me!'), + ), + ) + ); + } + + function test_tokenizeHTML_characterEntity() { + $this->assertTokenization( + 'θ', + array( new HTMLPurifier_Token_Text("\xCE\xB8") ) + ); + } + + function test_tokenizeHTML_characterEntityInCDATA() { + $this->assertTokenization( + '<![CDATA[→]]>', + array( new HTMLPurifier_Token_Text("→") ), + array( + 'PH5P' => array( + new HTMLPurifier_Token_Text('&'), + new HTMLPurifier_Token_Text('rarr;'), + ), + ) + ); + } + + function test_tokenizeHTML_entityInAttribute() { + $this->assertTokenization( + '<a href="index.php?title=foo&id=bar">Link</a>', + array( + new HTMLPurifier_Token_Start('a',array('href' => 'index.php?title=foo&id=bar')), + new HTMLPurifier_Token_Text('Link'), + new HTMLPurifier_Token_End('a'), + ) + ); + } + + function test_tokenizeHTML_preserveUTF8() { + $this->assertTokenization( + "\xCE\xB8", + array( new HTMLPurifier_Token_Text("\xCE\xB8") ) + ); + } + + function test_tokenizeHTML_specialEntityInAttribute() { + $this->assertTokenization( + '<br test="x < 6" />', + array( new HTMLPurifier_Token_Empty('br', array('test' => 'x < 6')) ) + ); + } + + function test_tokenizeHTML_emoticonProtection() { + $this->assertTokenization( + '<b>Whoa! <3 That\'s not good >.></b>', + array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('Whoa! '), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('3 That\'s not good >.>'), + new HTMLPurifier_Token_End('b') + ), + array( + // text is absorbed together + 'DOMLex' => array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('Whoa! <3 That\'s not good >.>'), + new HTMLPurifier_Token_End('b'), + ), + 'PH5P' => array( // interesting grouping + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('Whoa! '), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('3 That\'s not good >.>'), + new HTMLPurifier_Token_End('b'), + ), + ) + ); + } + + function test_tokenizeHTML_commentWithFunkyChars() { + $this->assertTokenization( + '<!-- This >< comment --><br />', + array( + new HTMLPurifier_Token_Comment(' This >< comment '), + new HTMLPurifier_Token_Empty('br'), + ) + ); + } + + function test_tokenizeHTML_unterminatedComment() { + $this->assertTokenization( + '<!-- This >< comment', + array( new HTMLPurifier_Token_Comment(' This >< comment') ), + array( + 'DOMLex' => false, + 'PH5P' => false, + ) + ); + } + + function test_tokenizeHTML_scriptCDATAContents() { + $this->config->set('HTML.Trusted', true); + $this->assertTokenization( + 'Foo: <script>alert("<foo>");</script>', + array( + new HTMLPurifier_Token_Text('Foo: '), + new HTMLPurifier_Token_Start('script'), + new HTMLPurifier_Token_Text('alert("<foo>");'), + new HTMLPurifier_Token_End('script'), + ), + array( + // PH5P, for some reason, bubbles the script to <head> + 'PH5P' => false, + ) + ); + } + + function test_tokenizeHTML_entitiesInComment() { + $this->assertTokenization( + '<!-- This comment < < & -->', + array( new HTMLPurifier_Token_Comment(' This comment < < & ') ) + ); + } + + function test_tokenizeHTML_attributeWithSpecialCharacters() { + $this->assertTokenization( + '<a href="><>">', + array( new HTMLPurifier_Token_Empty('a', array('href' => '><>')) ), + array( + 'DirectLex' => array( + new HTMLPurifier_Token_Start('a', array('href' => '')), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('">'), + ) + ) + ); + } + + function test_tokenizeHTML_emptyTagWithSlashInAttribute() { + $this->assertTokenization( + '<param name="src" value="http://example.com/video.wmv" />', + array( new HTMLPurifier_Token_Empty('param', array('name' => 'src', 'value' => 'http://example.com/video.wmv')) ) + ); + } + + function test_tokenizeHTML_style() { + $extra = array( + // PH5P doesn't seem to like style tags + 'PH5P' => false, + // DirectLex defers to RemoveForeignElements for textification + 'DirectLex' => array( + new HTMLPurifier_Token_Start('style', array('type' => 'text/css')), + new HTMLPurifier_Token_Comment("\ndiv {}\n"), + new HTMLPurifier_Token_End('style'), + ), + ); + if (!defined('LIBXML_VERSION')) { + // LIBXML_VERSION is missing in early versions of PHP + // prior to 1.30 of php-src/ext/libxml/libxml.c (version-wise, + // this translates to 5.0.x. In such cases, punt the test entirely. + return; + } elseif (LIBXML_VERSION < 20628) { + // libxml's behavior is wrong prior to this version, so make + // appropriate accomodations + $extra['DOMLex'] = $extra['DirectLex']; + } + $this->assertTokenization( +'<style type="text/css"><!-- +div {} +--></style>', + array( + new HTMLPurifier_Token_Start('style', array('type' => 'text/css')), + new HTMLPurifier_Token_Text("\ndiv {}\n"), + new HTMLPurifier_Token_End('style'), + ), + $extra + ); + } + + function test_tokenizeHTML_tagWithAtSignAndExtraGt() { + $alt_expect = array( + // Technically this is invalid, but it won't be a + // problem with invalid element removal; also, this + // mimics Mozilla's parsing of the tag. + new HTMLPurifier_Token_Start('a@'), + new HTMLPurifier_Token_Text('>'), + ); + $this->assertTokenization( + '<a@>>', + array( + new HTMLPurifier_Token_Start('a'), + new HTMLPurifier_Token_Text('>'), + new HTMLPurifier_Token_End('a'), + ), + array( + 'DirectLex' => $alt_expect, + ) + ); + } + + function test_tokenizeHTML_emoticonHeart() { + $this->assertTokenization( + '<br /><3<br />', + array( + new HTMLPurifier_Token_Empty('br'), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('3'), + new HTMLPurifier_Token_Empty('br'), + ), + array( + 'DOMLex' => array( + new HTMLPurifier_Token_Empty('br'), + new HTMLPurifier_Token_Text('<3'), + new HTMLPurifier_Token_Empty('br'), + ), + ) + ); + } + + function test_tokenizeHTML_emoticonShiftyEyes() { + $this->assertTokenization( + '<b><<</b>', + array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_End('b'), + ), + array( + 'DOMLex' => array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('<<'), + new HTMLPurifier_Token_End('b'), + ), + ) + ); + } + + function test_tokenizeHTML_eon1996() { + $this->assertTokenization( + '< <b>test</b>', + array( + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text(' '), + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('test'), + new HTMLPurifier_Token_End('b'), + ), + array( + 'DOMLex' => array( + new HTMLPurifier_Token_Text('< '), + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('test'), + new HTMLPurifier_Token_End('b'), + ), + ) + ); + } + + function test_tokenizeHTML_bodyInCDATA() { + $alt_tokens = array( + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('body'), + new HTMLPurifier_Token_Text('>'), + new HTMLPurifier_Token_Text('Foo'), + new HTMLPurifier_Token_Text('<'), + new HTMLPurifier_Token_Text('/body'), + new HTMLPurifier_Token_Text('>'), + ); + $this->assertTokenization( + '<![CDATA[<body>Foo</body>]]>', + array( + new HTMLPurifier_Token_Text('<body>Foo</body>'), + ), + array( + 'PH5P' => $alt_tokens, + ) + ); + } + + function test_tokenizeHTML_() { + $this->assertTokenization( + '<a><img /></a>', + array( + new HTMLPurifier_Token_Start('a'), + new HTMLPurifier_Token_Empty('img'), + new HTMLPurifier_Token_End('a'), + ) + ); + } + + function test_tokenizeHTML_ignoreIECondComment() { + $this->assertTokenization( + '<!--[if IE]>foo<a>bar<!-- baz --><![endif]-->', + array() + ); + } + + function test_tokenizeHTML_removeProcessingInstruction() { + $this->config->set('Core.RemoveProcessingInstructions', true); + $this->assertTokenization( + '<?xml blah blah ?>', + array() + ); + } + + function test_tokenizeHTML_removeNewline() { + $this->config->set('Core.NormalizeNewlines', true); + $this->assertTokenization( + "plain\rtext\r\n", + array( + new HTMLPurifier_Token_Text("plain\ntext\n") + ) + ); + } + + function test_tokenizeHTML_noRemoveNewline() { + $this->config->set('Core.NormalizeNewlines', false); + $this->assertTokenization( + "plain\rtext\r\n", + array( + new HTMLPurifier_Token_Text("plain\rtext\r\n") + ) + ); + } + + function test_tokenizeHTML_conditionalCommentUngreedy() { + $this->assertTokenization( + '<!--[if gte mso 9]>a<![endif]-->b<!--[if gte mso 9]>c<![endif]-->', + array( + new HTMLPurifier_Token_Text("b") + ) + ); + } + + function test_tokenizeHTML_imgTag() { + $start = array( + new HTMLPurifier_Token_Start('img', + array( + 'src' => 'img_11775.jpg', + 'alt' => '[Img #11775]', + 'id' => 'EMBEDDED_IMG_11775', + ) + ) + ); + $this->assertTokenization( + '<img src="img_11775.jpg" alt="[Img #11775]" id="EMBEDDED_IMG_11775" >', + array( + new HTMLPurifier_Token_Empty('img', + array( + 'src' => 'img_11775.jpg', + 'alt' => '[Img #11775]', + 'id' => 'EMBEDDED_IMG_11775', + ) + ) + ), + array( + 'DirectLex' => $start, + ) + ); + } + + + /* + + function test_tokenizeHTML_() { + $this->assertTokenization( + , + array( + + ) + ); + } + */ + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/domxml.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/domxml.phpt new file mode 100644 index 000000000..406ac400b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/domxml.phpt @@ -0,0 +1,15 @@ +--TEST-- +DirectLex with domxml test +--SKIPIF-- +<?php +if (!extension_loaded('dom')) { + echo "skip - dom not available"; +} elseif (!extension_loaded('domxml')) { + echo "skip - domxml not loaded"; +} +--FILE-- +<?php +require '../library/HTMLPurifier.auto.php'; +echo get_class(HTMLPurifier_Lexer::create(HTMLPurifier_Config::createDefault())); +--EXPECT-- +HTMLPurifier_Lexer_DirectLex
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/func.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/func.phpt new file mode 100644 index 000000000..d194d68d3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/func.phpt @@ -0,0 +1,9 @@ +--TEST-- +HTMLPurifier.func.php test +--FILE-- +<?php +require '../library/HTMLPurifier.auto.php'; +require 'HTMLPurifier.func.php'; +echo HTMLPurifier('<b>Salsa!'); +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/kses/basic.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/kses/basic.phpt new file mode 100644 index 000000000..8a9cd0162 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/kses/basic.phpt @@ -0,0 +1,15 @@ +--TEST-- +HTMLPurifier.kses.php basic test +--FILE-- +<?php +require '../library/HTMLPurifier.kses.php'; +echo kses( + '<a class="foo" style="color:#F00;" href="https://google.com">Foo<i>Bar</i>', + array( + 'a' => array('class' => 1, 'href' => 1), + ), + array('http') // no https! +); + +--EXPECT-- +<a class="foo">FooBar</a>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_autoload.inc b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_autoload.inc new file mode 100644 index 000000000..42e3500e4 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_autoload.inc @@ -0,0 +1,12 @@ +<?php + +/** + * Tests if autoloading for HTML Purifier is enabled. If all tests pass, + * output is blank. + */ + +assert("!in_array(realpath('../library/HTMLPurifier/Filter/YouTube.php'), get_included_files())"); +new HTMLPurifier_Filter_YouTube(); +assert(" in_array(realpath('../library/HTMLPurifier.autoload.php'), get_included_files())"); + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_no-autoload.inc b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_no-autoload.inc new file mode 100644 index 000000000..fc109e727 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/_no-autoload.inc @@ -0,0 +1,17 @@ +<?php + +/** + * Tests if autoloading is off in HTML Purifier. If all tests pass, no output. + */ + +if (function_exists('spl_autoload_register')) { + $__v = spl_autoload_functions(); + assert('$__v == false || !in_array(array("HTMLPurifier_Bootstrap", "autoload"), $__v)'); +} else { + if (function_exists('__autoload')) { + $__r = new ReflectionFunction('__autoload'); + assert('$__r->getFileName() != realpath("../library/HTMLPurifier.autoload.php")'); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-includes.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-includes.phpt new file mode 100644 index 000000000..6a8f909b8 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-includes.phpt @@ -0,0 +1,12 @@ +--TEST-- +HTMLPurifier.auto.php and HTMLPurifier.includes.php loading test +--FILE-- +<?php +require '../library/HTMLPurifier.path.php'; +require 'HTMLPurifier.includes.php'; +require 'HTMLPurifier/PHPT/loading/_no-autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!'); +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-autoload.phpt new file mode 100644 index 000000000..aad437202 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-autoload.phpt @@ -0,0 +1,28 @@ +--TEST-- +HTMLPurifier.auto.php using spl_autoload_register with __autoload() already defined loading test +--SKIPIF-- +<?php +if (!function_exists('spl_autoload_register')) { + echo "skip - spl_autoload_register() not available"; +} +--FILE-- +<?php +function __autoload($class) { + echo "Autoloading $class... +"; + eval("class $class {}"); +} + +require '../library/HTMLPurifier.auto.php'; +require 'HTMLPurifier/PHPT/loading/_autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!') . " +"; + +// purposely invoke older autoload +$bar = new Bar(); + +--EXPECT-- +<b>Salsa!</b> +Autoloading Bar...
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload-default.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload-default.phpt new file mode 100644 index 000000000..a4011f1d3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload-default.phpt @@ -0,0 +1,25 @@ +--TEST-- +HTMLPurifier.auto.php using spl_autoload_register default +--SKIPIF-- +<?php +if (!function_exists('spl_autoload_register')) { + echo "skip - spl_autoload_register() not available"; +} +--FILE-- +<?php +spl_autoload_extensions(".php"); +spl_autoload_register(); + +require '../library/HTMLPurifier.auto.php'; +require 'HTMLPurifier/PHPT/loading/_autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!') . " +"; + +// purposely invoke standard autoload +$test = new default_load(); + +--EXPECT-- +<b>Salsa!</b> +Default loaded diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload.phpt new file mode 100644 index 000000000..1697bb13f --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-with-spl-autoload.phpt @@ -0,0 +1,43 @@ +--TEST-- +HTMLPurifier.auto.php using spl_autoload_register with user registration loading test +--SKIPIF-- +<?php +if (!function_exists('spl_autoload_register')) { + echo "skip - spl_autoload_register() not available"; +} +--FILE-- +<?php +function my_autoload($class) { + echo "Autoloading $class... +"; + eval("class $class {}"); + return true; +} +class MyClass { + public static function myAutoload($class) { + if ($class == 'Foo') { + echo "Special autoloading Foo... +"; + eval("class $class {}"); + } + } +} + +spl_autoload_register(array('MyClass', 'myAutoload')); +spl_autoload_register('my_autoload'); + +require '../library/HTMLPurifier.auto.php'; +require 'HTMLPurifier/PHPT/loading/_autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!') . " +"; + +// purposely invoke older autoloads +$foo = new Foo(); +$bar = new Bar(); + +--EXPECT-- +<b>Salsa!</b> +Special autoloading Foo... +Autoloading Bar...
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-autoload.phpt new file mode 100644 index 000000000..aeee9dcee --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-autoload.phpt @@ -0,0 +1,19 @@ +--TEST-- +HTMLPurifier.auto.php without spl_autoload_register without userland autoload loading test +--SKIPIF-- +<?php +if (function_exists('spl_autoload_register')) { + echo "skip - spl_autoload_register() available"; +} +--FILE-- +<?php +assert("!function_exists('__autoload')"); +require '../library/HTMLPurifier.auto.php'; +require 'HTMLPurifier/PHPT/loading/_autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!') . " +"; + +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-with-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-with-autoload.phpt new file mode 100644 index 000000000..2b6f49d8c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto-without-spl-with-autoload.phpt @@ -0,0 +1,21 @@ +--TEST-- +HTMLPurifier.auto.php without spl_autoload_register but with userland +__autoload() defined test +--SKIPIF-- +<?php +if (function_exists('spl_autoload_register')) { + echo "skip - spl_autoload_register() available"; +} +--FILE-- +<?php +function __autoload($class) { + echo "Autoloading $class... +"; + eval("class $class {}"); +} +require '../library/HTMLPurifier.auto.php'; +require 'HTMLPurifier/PHPT/loading/_no-autoload.inc'; +$purifier = new HTMLPurifier(); + +--EXPECT-- +Autoloading HTMLPurifier...
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto.phpt new file mode 100644 index 000000000..a4ea716f9 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/auto.phpt @@ -0,0 +1,11 @@ +--TEST-- +HTMLPurifier.auto.php loading test +--FILE-- +<?php +require '../library/HTMLPurifier.auto.php'; +require 'HTMLPurifier/PHPT/loading/_autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!'); +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/error-auto-with-spl-nonstatic-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/error-auto-with-spl-nonstatic-autoload.phpt new file mode 100644 index 000000000..9a91abaf8 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/error-auto-with-spl-nonstatic-autoload.phpt @@ -0,0 +1,32 @@ +--TEST-- +Error when registering autoload with non-static autoload already on SPL stack +--SKIPIF-- +<?php +if (!function_exists('spl_autoload_register')) { + echo "skip - spl_autoload_register() not available"; +} +if (version_compare(PHP_VERSION, '5.2.11', '>=')) { + echo "skip - non-buggy version of PHP"; +} +--FILE-- +<?php +class NotStatic +{ + public function autoload($class) { + echo "Autoloading... $class" . PHP_EOL; + eval("class $class {}"); + } +} + +$obj = new NotStatic(); +spl_autoload_register(array($obj, 'autoload')); + +try { + require '../library/HTMLPurifier.auto.php'; +} catch (Exception $e) { + echo 'Caught error gracefully'; + assert('strpos($e->getMessage(), "44144") !== false'); +} + +--EXPECT-- +Caught error gracefully diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes-autoload.phpt new file mode 100644 index 000000000..6120956c6 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes-autoload.phpt @@ -0,0 +1,14 @@ +--TEST-- +HTMLPurifier.path.php, HTMLPurifier.includes.php and HTMLPurifier.autoload.php loading test +--FILE-- +<?php +require '../library/HTMLPurifier.path.php'; +require 'HTMLPurifier.includes.php'; +require 'HTMLPurifier.autoload.php'; +require 'HTMLPurifier/PHPT/loading/_autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!'); + +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes.phpt new file mode 100644 index 000000000..681d51a96 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/path-includes.phpt @@ -0,0 +1,12 @@ +--TEST-- +HTMLPurifier.path.php and HTMLPurifier.includes.php loading test +--FILE-- +<?php +require '../library/HTMLPurifier.path.php'; +require 'HTMLPurifier.includes.php'; +require 'HTMLPurifier/PHPT/loading/_no-autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!'); +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/safe-includes.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/safe-includes.phpt new file mode 100644 index 000000000..cb6f95d53 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/safe-includes.phpt @@ -0,0 +1,12 @@ +--TEST-- +HTMLPurifier.safe-includes.php loading test +--FILE-- +<?php +require_once '../library/HTMLPurifier.php'; // Tests for require_once +require_once '../library/HTMLPurifier.safe-includes.php'; +require 'HTMLPurifier/PHPT/loading/_no-autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!'); +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-autoload.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-autoload.phpt new file mode 100644 index 000000000..36bb2efd7 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-autoload.phpt @@ -0,0 +1,12 @@ +--TEST-- +HTMLPurifier.standalone.php loading test +--FILE-- +<?php +require '../library/HTMLPurifier.standalone.php'; +require '../library/HTMLPurifier.autoload.php'; +require 'HTMLPurifier/PHPT/loading/_autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!'); +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-with-prefix.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-with-prefix.phpt new file mode 100644 index 000000000..721dd7d17 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone-with-prefix.phpt @@ -0,0 +1,15 @@ +--TEST-- +HTMLPurifier.standalone.php with HTMLPURIFIER_PREFIX loading test +--FILE-- +<?php +define('HTMLPURIFIER_PREFIX', realpath('../library')); +require '../library/HTMLPurifier.path.php'; +require 'HTMLPurifier.standalone.php'; +require 'HTMLPurifier/Filter/YouTube.php'; +require 'HTMLPurifier/PHPT/loading/_no-autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!'); +assert('in_array(realpath("../library/HTMLPurifier/Filter/YouTube.php"), get_included_files())'); +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone.phpt new file mode 100644 index 000000000..a4fe4f77d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/loading/standalone.phpt @@ -0,0 +1,13 @@ +--TEST-- +HTMLPurifier.standalone.php loading test +--FILE-- +<?php +require '../library/HTMLPurifier.standalone.php'; +require 'HTMLPurifier/Filter/YouTube.php'; +require 'HTMLPurifier/PHPT/loading/_no-autoload.inc'; +$config = HTMLPurifier_Config::createDefault(); +$purifier = new HTMLPurifier($config); +echo $purifier->purify('<b>Salsa!'); +assert('in_array(realpath("../library/standalone/HTMLPurifier/Filter/YouTube.php"), get_included_files())'); +--EXPECT-- +<b>Salsa!</b>
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/stub.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/stub.phpt new file mode 100644 index 000000000..e919c5763 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/stub.phpt @@ -0,0 +1,6 @@ +--TEST-- +PHPT testing framework smoketest +--FILE-- +Foobar +--EXPECT-- +Foobar
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/utf8.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/utf8.phpt new file mode 100644 index 000000000..87c20ef66 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/utf8.phpt @@ -0,0 +1,9 @@ +--TEST-- +UTF-8 smoketest +--FILE-- +<?php +require '../library/HTMLPurifier.auto.php'; +$purifier = new HTMLPurifier(); +echo $purifier->purify('太極拳, ЊЎЖ, لمنس'); +--EXPECT-- +太極拳, ЊЎЖ, لمنس
\ No newline at end of file diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PHPT/ze1_compatibility_mode.phpt b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/ze1_compatibility_mode.phpt new file mode 100644 index 000000000..606d7592e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PHPT/ze1_compatibility_mode.phpt @@ -0,0 +1,14 @@ +--TEST-- +Error with zend.ze1_compatibility_mode test +--PRESKIPIF-- +<?php +if (version_compare(PHP_VERSION, '5.3.0') >= 0) { + echo 'skip - ze1_compatibility_mode not present in PHP 5.3 or later'; +} +--INI-- +zend.ze1_compatibility_mode = 1 +--FILE-- +<?php +require '../library/HTMLPurifier.auto.php'; +--EXPECTF-- +Fatal error: HTML Purifier is not compatible with zend.ze1_compatibility_mode; please turn it off in %s diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PercentEncoderTest.php b/lib/htmlpurifier/tests/HTMLPurifier/PercentEncoderTest.php new file mode 100644 index 000000000..fa969de13 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PercentEncoderTest.php @@ -0,0 +1,63 @@ +<?php + +class HTMLPurifier_PercentEncoderTest extends HTMLPurifier_Harness +{ + + protected $PercentEncoder; + protected $func; + + function setUp() { + $this->PercentEncoder = new HTMLPurifier_PercentEncoder(); + $this->func = ''; + } + + function assertDecode($string, $expect = true) { + if ($expect === true) $expect = $string; + $this->assertIdentical($this->PercentEncoder->{$this->func}($string), $expect); + } + + function test_normalize() { + $this->func = 'normalize'; + + $this->assertDecode('Aw.../-$^8'); // no change + $this->assertDecode('%41%77%7E%2D%2E%5F', 'Aw~-._'); // decode unreserved chars + $this->assertDecode('%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D'); // preserve reserved chars + $this->assertDecode('%2b', '%2B'); // normalize to uppercase + $this->assertDecode('%2B2B%3A3A'); // extra text + $this->assertDecode('%2b2B%4141', '%2B2BA41'); // extra text, with normalization + $this->assertDecode('%', '%25'); // normalize stray percent sign + $this->assertDecode('%5%25', '%255%25'); // permaturely terminated encoding + $this->assertDecode('%GJ', '%25GJ'); // invalid hexadecimal chars + + // contested behavior, if this changes, we'll also have to have + // outbound encoding + $this->assertDecode('%FC'); // not reserved or unreserved, preserve + + } + + function assertEncode($string, $expect = true, $preserve = false) { + if ($expect === true) $expect = $string; + $encoder = new HTMLPurifier_PercentEncoder($preserve); + $result = $encoder->encode($string); + $this->assertIdentical($result, $expect); + } + + function test_encode_noChange() { + $this->assertEncode('abc012-_~.'); + } + + function test_encode_encode() { + $this->assertEncode('>', '%3E'); + } + + function test_encode_preserve() { + $this->assertEncode('<>', '<%3E', '<'); + } + + function test_encode_low() { + $this->assertEncode("\1", '%01'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/PropertyListTest.php b/lib/htmlpurifier/tests/HTMLPurifier/PropertyListTest.php new file mode 100644 index 000000000..a2d4811ef --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/PropertyListTest.php @@ -0,0 +1,93 @@ +<?php + +class HTMLPurifier_PropertyListTest extends UnitTestCase +{ + + function testBasic() { + $plist = new HTMLPurifier_PropertyList(); + $plist->set('key', 'value'); + $this->assertIdentical($plist->get('key'), 'value'); + } + + function testNotFound() { + $this->expectException(new HTMLPurifier_Exception("Key 'key' not found")); + $plist = new HTMLPurifier_PropertyList(); + $plist->get('key'); + } + + function testRecursion() { + $parent_plist = new HTMLPurifier_PropertyList(); + $parent_plist->set('key', 'value'); + $plist = new HTMLPurifier_PropertyList(); + $plist->setParent($parent_plist); + $this->assertIdentical($plist->get('key'), 'value'); + } + + function testOverride() { + $parent_plist = new HTMLPurifier_PropertyList(); + $parent_plist->set('key', 'value'); + $plist = new HTMLPurifier_PropertyList(); + $plist->setParent($parent_plist); + $plist->set('key', 'value2'); + $this->assertIdentical($plist->get('key'), 'value2'); + } + + function testRecursionNotFound() { + $this->expectException(new HTMLPurifier_Exception("Key 'key' not found")); + $parent_plist = new HTMLPurifier_PropertyList(); + $plist = new HTMLPurifier_PropertyList(); + $plist->setParent($parent_plist); + $this->assertIdentical($plist->get('key'), 'value'); + } + + function testHas() { + $plist = new HTMLPurifier_PropertyList(); + $this->assertIdentical($plist->has('key'), false); + $plist->set('key', 'value'); + $this->assertIdentical($plist->has('key'), true); + } + + function testReset() { + $plist = new HTMLPurifier_PropertyList(); + $plist->set('key1', 'value'); + $plist->set('key2', 'value'); + $plist->set('key3', 'value'); + $this->assertIdentical($plist->has('key1'), true); + $this->assertIdentical($plist->has('key2'), true); + $this->assertIdentical($plist->has('key3'), true); + $plist->reset('key2'); + $this->assertIdentical($plist->has('key1'), true); + $this->assertIdentical($plist->has('key2'), false); + $this->assertIdentical($plist->has('key3'), true); + $plist->reset(); + $this->assertIdentical($plist->has('key1'), false); + $this->assertIdentical($plist->has('key2'), false); + $this->assertIdentical($plist->has('key3'), false); + } + + function testSquash() { + $parent = new HTMLPurifier_PropertyList(); + $parent->set('key1', 'hidden'); + $parent->set('key2', 2); + $plist = new HTMLPurifier_PropertyList($parent); + $plist->set('key1', 1); + $plist->set('key3', 3); + $this->assertIdentical( + $plist->squash(), + array('key1' => 1, 'key2' => 2, 'key3' => 3) + ); + // updates don't show up... + $plist->set('key2', 22); + $this->assertIdentical( + $plist->squash(), + array('key1' => 1, 'key2' => 2, 'key3' => 3) + ); + // until you force + $this->assertIdentical( + $plist->squash(true), + array('key1' => 1, 'key2' => 22, 'key3' => 3) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/Reporter.php b/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/Reporter.php new file mode 100644 index 000000000..b9da9ab0b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/Reporter.php @@ -0,0 +1,60 @@ +<?php + +class HTMLPurifier_SimpleTest_Reporter extends HTMLReporter +{ + + protected $ac; + + public function __construct($encoding, $ac) { + $this->ac = $ac; + parent::__construct($encoding); + } + + public function paintHeader($test_name) { + parent::paintHeader($test_name); +?> +<form action="" method="get" id="select"> + <select name="f"> + <option value="" style="font-weight:bold;"<?php if(!$this->ac['file']) {echo ' selected';} ?>>All Tests</option> + <?php foreach($GLOBALS['HTMLPurifierTest']['Files'] as $file) { ?> + <option value="<?php echo $file ?>"<?php + if ($this->ac['file'] == $file) echo ' selected'; + ?>><?php echo $file ?></option> + <?php } ?> + </select> + <input type="checkbox" name="standalone" value="1" title="Standalone version?" <?php if($this->ac['standalone']) {echo 'checked="checked" ';} ?>/> + <input type="submit" value="Go"> +</form> +<?php + flush(); + } + + public function paintFooter($test_name) { + if (function_exists('xdebug_peak_memory_usage')) { + $max_mem = number_format(xdebug_peak_memory_usage()); + echo "<div>Max memory usage: $max_mem bytes</div>"; + } + parent::paintFooter($test_name); + } + + protected function getCss() { + $css = parent::getCss(); + $css .= ' + #select {position:absolute;top:0.2em;right:0.2em;} + '; + return $css; + } + + function getTestList() { + // hacky; depends on a specific implementation of paintPass, etc. + $list = parent::getTestList(); + $testcase = $list[1]; + if (class_exists($testcase, false)) $file = str_replace('_', '/', $testcase) . '.php'; + else $file = $testcase; + $list[1] = '<a href="index.php?file=' . $file . '">' . $testcase . '</a>'; + return $list; + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php b/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php new file mode 100644 index 000000000..56204e7a3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php @@ -0,0 +1,21 @@ +<?php + +class HTMLPurifier_SimpleTest_TextReporter extends TextReporter { + protected $verbose = false; + function __construct($AC) { + parent::__construct(); + $this->verbose = $AC['verbose']; + } + function paintPass($message) { + parent::paintPass($message); + if ($this->verbose) { + print 'Pass ' . $this->getPassCount() . ") $message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php new file mode 100644 index 000000000..8be422cc9 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php @@ -0,0 +1,66 @@ +<?php + +class HTMLPurifier_Strategy_Composite_Test + extends HTMLPurifier_Strategy_Composite +{ + + public function __construct(&$strategies) { + $this->strategies =& $strategies; + } + +} + +// doesn't use Strategy harness +class HTMLPurifier_Strategy_CompositeTest extends HTMLPurifier_Harness +{ + + function test() { + + generate_mock_once('HTMLPurifier_Strategy'); + generate_mock_once('HTMLPurifier_Config'); + generate_mock_once('HTMLPurifier_Context'); + + // setup a bunch of mock strategies to inject into our composite test + + $mock_1 = new HTMLPurifier_StrategyMock(); + $mock_2 = new HTMLPurifier_StrategyMock(); + $mock_3 = new HTMLPurifier_StrategyMock(); + + // setup the object + + $strategies = array(&$mock_1, &$mock_2, &$mock_3); + $composite = new HTMLPurifier_Strategy_Composite_Test($strategies); + + // setup expectations + + $input_1 = 'This is raw data'; + $input_2 = 'Processed by 1'; + $input_3 = 'Processed by 1 and 2'; + $input_4 = 'Processed by 1, 2 and 3'; // expected output + + $config = new HTMLPurifier_ConfigMock(); + $context = new HTMLPurifier_ContextMock(); + + $params_1 = array($input_1, $config, $context); + $params_2 = array($input_2, $config, $context); + $params_3 = array($input_3, $config, $context); + + $mock_1->expectOnce('execute', $params_1); + $mock_1->setReturnValue('execute', $input_2, $params_1); + + $mock_2->expectOnce('execute', $params_2); + $mock_2->setReturnValue('execute', $input_3, $params_2); + + $mock_3->expectOnce('execute', $params_3); + $mock_3->setReturnValue('execute', $input_4, $params_3); + + // perform test + + $output = $composite->execute($input_1, $config, $context); + $this->assertIdentical($input_4, $output); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php new file mode 100644 index 000000000..9bca5f60a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php @@ -0,0 +1,45 @@ +<?php + +class HTMLPurifier_Strategy_CoreTest extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_Core(); + } + + function testBlankInput() { + $this->assertResult(''); + } + + function testMakeWellFormed() { + $this->assertResult( + '<b>Make well formed.', + '<b>Make well formed.</b>' + ); + } + + function testFixNesting() { + $this->assertResult( + '<b><div>Fix nesting.</div></b>', + '<b></b><div><b>Fix nesting.</b></div><b></b>' + ); + } + + function testRemoveForeignElements() { + $this->assertResult( + '<asdf>Foreign element removal.</asdf>', + 'Foreign element removal.' + ); + } + + function testFirstThree() { + $this->assertResult( + '<foo><b><div>All three.</div></b>', + '<b></b><div><b>All three.</b></div><b></b>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php new file mode 100644 index 000000000..0e1bca681 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php @@ -0,0 +1,18 @@ +<?php + +class HTMLPurifier_Strategy_ErrorsHarness extends HTMLPurifier_ErrorsHarness +{ + + // needs to be defined + protected function getStrategy() {} + + protected function invoke($input) { + $strategy = $this->getStrategy(); + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $tokens = $lexer->tokenizeHTML($input, $this->config, $this->context); + $strategy->execute($tokens, $this->config, $this->context); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php new file mode 100644 index 000000000..9394352ec --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php @@ -0,0 +1,144 @@ +<?php + +class HTMLPurifier_Strategy_FixNestingTest extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_FixNesting(); + } + + function testPreserveInlineInRoot() { + $this->assertResult('<b>Bold text</b>'); + } + + function testPreserveInlineAndBlockInRoot() { + $this->assertResult('<a href="about:blank">Blank</a><div>Block</div>'); + } + + function testRemoveBlockInInline() { + $this->assertResult( + '<b><div>Illegal div.</div></b>', + '<b>Illegal div.</b>' + ); + } + + function testEscapeBlockInInline() { + $this->config->set('Core.EscapeInvalidChildren', true); + $this->assertResult( + '<b><div>Illegal div.</div></b>', + '<b><div>Illegal div.</div></b>' + ); + } + + function testRemoveNodeWithMissingRequiredElements() { + $this->assertResult('<ul></ul>', ''); + } + + function testListHandleIllegalPCDATA() { + $this->assertResult( + '<ul>Illegal text<li>Legal item</li></ul>', + '<ul><li>Illegal text</li><li>Legal item</li></ul>' + ); + } + + function testRemoveIllegalPCDATA() { + $this->assertResult( + '<table><tr>Illegal text<td></td></tr></table>', + '<table><tr><td></td></tr></table>' + ); + } + + function testCustomTableDefinition() { + $this->assertResult('<table><tr><td>Cell 1</td></tr></table>'); + } + + function testRemoveEmptyTable() { + $this->assertResult('<table></table>', ''); + } + + function testChameleonRemoveBlockInNodeInInline() { + $this->assertResult( + '<span><ins><div>Not allowed!</div></ins></span>', + '<span><ins>Not allowed!</ins></span>' + ); + } + + function testChameleonRemoveBlockInBlockNodeWithInlineContent() { + $this->assertResult( + '<h1><ins><div>Not allowed!</div></ins></h1>', + '<h1><ins>Not allowed!</ins></h1>' + ); + } + + function testNestedChameleonRemoveBlockInNodeWithInlineContent() { + $this->assertResult( + '<h1><ins><del><div>Not allowed!</div></del></ins></h1>', + '<h1><ins><del>Not allowed!</del></ins></h1>' + ); + } + + function testNestedChameleonPreserveBlockInBlock() { + $this->assertResult( + '<div><ins><del><div>Allowed!</div></del></ins></div>' + ); + } + + function testChameleonEscapeInvalidBlockInInline() { + $this->config->set('Core.EscapeInvalidChildren', true); + $this->assertResult( // alt config + '<span><ins><div>Not allowed!</div></ins></span>', + '<span><ins><div>Not allowed!</div></ins></span>' + ); + } + + function testExclusionsIntegration() { + // test exclusions + $this->assertResult( + '<a><span><a>Not allowed</a></span></a>', + '<a><span></span></a>' + ); + } + + function testPreserveInlineNodeInInlineRootNode() { + $this->config->set('HTML.Parent', 'span'); + $this->assertResult('<b>Bold</b>'); + } + + function testRemoveBlockNodeInInlineRootNode() { + $this->config->set('HTML.Parent', 'span'); + $this->assertResult('<div>Reject</div>', 'Reject'); + } + + function testInvalidParentError() { + // test fallback to div + $this->config->set('HTML.Parent', 'obviously-impossible'); + $this->config->set('Cache.DefinitionImpl', null); + $this->expectError('Cannot use unrecognized element as parent'); + $this->assertResult('<div>Accept</div>'); + } + + function testCascadingRemovalOfNodesMissingRequiredChildren() { + $this->assertResult('<table><tr></tr></table>', ''); + } + + function testCascadingRemovalSpecialCaseCannotScrollOneBack() { + $this->assertResult('<table><tr></tr><tr></tr></table>', ''); + } + + function testLotsOfCascadingRemovalOfNodes() { + $this->assertResult('<table><tbody><tr></tr><tr></tr></tbody><tr></tr><tr></tr></table>', ''); + } + + function testAdjacentRemovalOfNodeMissingRequiredChildren() { + $this->assertResult('<table></table><table></table>', ''); + } + + function testStrictBlockquoteInHTML401() { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult('<blockquote>text</blockquote>', '<blockquote><p>text</p></blockquote>'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php new file mode 100644 index 000000000..468480774 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php @@ -0,0 +1,41 @@ +<?php + +class HTMLPurifier_Strategy_FixNesting_ErrorsTest extends HTMLPurifier_Strategy_ErrorsHarness +{ + + protected function getStrategy() { + return new HTMLPurifier_Strategy_FixNesting(); + } + + function testNodeRemoved() { + $this->expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('ul', array(), 1)); + $this->invoke('<ul></ul>'); + } + + function testNodeExcluded() { + $this->expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node excluded'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('a', array(), 2)); + $this->invoke("<a>\n<a></a></a>"); + } + + function testNodeReorganized() { + $this->expectErrorCollection(E_WARNING, 'Strategy_FixNesting: Node reorganized'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('span', array(), 1)); + $this->invoke("<span>Valid<div>Invalid</div></span>"); + } + + function testNoNodeReorganizedForEmptyNode() { + $this->expectNoErrorCollection(); + $this->invoke("<span></span>"); + } + + function testNodeContentsRemoved() { + $this->expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node contents removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('span', array(), 1)); + $this->invoke("<span><div></div></span>"); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php new file mode 100644 index 000000000..d1724bcb3 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php @@ -0,0 +1,18 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormed_EndInsertInjector extends HTMLPurifier_Injector +{ + public $name = 'EndInsertInjector'; + public $needed = array('span'); + public function handleEnd(&$token) { + if ($token->name == 'div') return; + $token = array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('Comment'), + new HTMLPurifier_Token_End('b'), + $token + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php new file mode 100644 index 000000000..72e833b71 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php @@ -0,0 +1,38 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormed_EndInsertInjectorTest extends HTMLPurifier_StrategyHarness +{ + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_EndInsertInjector() + )); + } + function testEmpty() { + $this->assertResult(''); + } + function testNormal() { + $this->assertResult('<i>Foo</i>', '<i>Foo<b>Comment</b></i>'); + } + function testEndOfDocumentProcessing() { + $this->assertResult('<i>Foo', '<i>Foo<b>Comment</b></i>'); + } + function testDoubleEndOfDocumentProcessing() { + $this->assertResult('<i><i>Foo', '<i><i>Foo<b>Comment</b></i><b>Comment</b></i>'); + } + function testEndOfNodeProcessing() { + $this->assertResult('<div><i>Foo</div>asdf', '<div><i>Foo<b>Comment</b></i></div><i>asdf<b>Comment</b></i>'); + } + function testEmptyToStartEndProcessing() { + $this->assertResult('<i />', '<i><b>Comment</b></i>'); + } + function testSpuriousEndTag() { + $this->assertResult('</i>', ''); + } + function testLessButStillSpuriousEndTag() { + $this->assertResult('<div></i></div>', '<div></div>'); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php new file mode 100644 index 000000000..21c491436 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php @@ -0,0 +1,29 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector extends HTMLPurifier_Injector +{ + public $name = 'EndRewindInjector'; + public $needed = array('span'); + public function handleElement(&$token) { + if (isset($token->_InjectorTest_EndRewindInjector_delete)) { + $token = false; + } + } + public function handleText(&$token) { + $token = false; + } + public function handleEnd(&$token) { + $i = null; + if ( + $this->backward($i, $prev) && + $prev instanceof HTMLPurifier_Token_Start && + $prev->name == 'span' + ) { + $token = false; + $prev->_InjectorTest_EndRewindInjector_delete = true; + $this->rewind($i); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php new file mode 100644 index 000000000..96c88748d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php @@ -0,0 +1,33 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjectorTest extends HTMLPurifier_StrategyHarness +{ + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector() + )); + } + function testBasic() { + $this->assertResult(''); + } + function testFunction() { + $this->assertResult('<span>asdf</span>',''); + } + function testFailedFunction() { + $this->assertResult('<span>asd<b>asdf</b>asdf</span>','<span><b></b></span>'); + } + function testPadded() { + $this->assertResult('<b></b><span>asdf</span><b></b>','<b></b><b></b>'); + } + function testDoubled() { + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector(), + new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector(), + )); + $this->assertResult('<b></b><span>asdf</span>', '<b></b>'); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php new file mode 100644 index 000000000..258346b8d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php @@ -0,0 +1,12 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormed_SkipInjector extends HTMLPurifier_Injector +{ + public $name = 'EndRewindInjector'; + public $needed = array('span'); + public function handleElement(&$token) { + $token = array(clone $token, clone $token); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjectorTest.php new file mode 100644 index 000000000..770b3d84e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjectorTest.php @@ -0,0 +1,27 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormed_SkipInjectorTest extends HTMLPurifier_StrategyHarness +{ + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector() + )); + } + function testEmpty() { + $this->assertResult(''); + } + function testMultiply() { + $this->assertResult('<br />', '<br /><br />'); + } + function testMultiplyMultiply() { + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector(), + new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector() + )); + $this->assertResult('<br />', '<br /><br /><br /><br />'); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php new file mode 100644 index 000000000..c6d775c5a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php @@ -0,0 +1,149 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormedTest extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_MakeWellFormed(); + } + + function testEmptyInput() { + $this->assertResult(''); + } + + function testWellFormedInput() { + $this->assertResult('This is <b>bold text</b>.'); + } + + function testUnclosedTagTerminatedByDocumentEnd() { + $this->assertResult( + '<b>Unclosed tag, gasp!', + '<b>Unclosed tag, gasp!</b>' + ); + } + + function testUnclosedTagTerminatedByParentNodeEnd() { + $this->assertResult( + '<b><i>Bold and italic?</b>', + '<b><i>Bold and italic?</i></b><i></i>' + ); + } + + function testRemoveStrayClosingTag() { + $this->assertResult( + 'Unused end tags... recycle!</b>', + 'Unused end tags... recycle!' + ); + } + + function testConvertStartToEmpty() { + $this->assertResult( + '<br style="clear:both;">', + '<br style="clear:both;" />' + ); + } + + function testConvertEmptyToStart() { + $this->assertResult( + '<div style="clear:both;" />', + '<div style="clear:both;"></div>' + ); + } + + function testAutoCloseParagraph() { + $this->assertResult( + '<p>Paragraph 1<p>Paragraph 2', + '<p>Paragraph 1</p><p>Paragraph 2</p>' + ); + } + + function testAutoCloseParagraphInsideDiv() { + $this->assertResult( + '<div><p>Paragraphs<p>In<p>A<p>Div</div>', + '<div><p>Paragraphs</p><p>In</p><p>A</p><p>Div</p></div>' + ); + } + + function testAutoCloseListItem() { + $this->assertResult( + '<ol><li>Item 1<li>Item 2</ol>', + '<ol><li>Item 1</li><li>Item 2</li></ol>' + ); + } + + function testAutoCloseColgroup() { + $this->assertResult( + '<table><colgroup><col /><tr></tr></table>', + '<table><colgroup><col /></colgroup><tr></tr></table>' + ); + } + + function testAutoCloseMultiple() { + $this->assertResult( + '<b><span><div></div>asdf', + '<b><span></span></b><div><b></b></div><b>asdf</b>' + ); + } + + function testUnrecognized() { + $this->assertResult( + '<asdf><foobar /><biddles>foo</asdf>', + '<asdf><foobar /><biddles>foo</biddles></asdf>' + ); + } + + function testBlockquoteWithInline() { + $this->config->set('HTML.Doctype', 'XHTML 1.0 Strict'); + $this->assertResult( + // This is actually invalid, but will be fixed by + // ChildDef_StrictBlockquote + '<blockquote>foo<b>bar</b></blockquote>' + ); + } + + function testLongCarryOver() { + $this->assertResult( + '<b>asdf<div>asdf<i>df</i></div>asdf</b>', + '<b>asdf</b><div><b>asdf<i>df</i></b></div><b>asdf</b>' + ); + } + + function testInterleaved() { + $this->assertResult( + '<u>foo<i>bar</u>baz</i>', + '<u>foo<i>bar</i></u><i>baz</i>' + ); + } + + function testNestedOl() { + $this->assertResult( + '<ol><ol><li>foo</li></ol></ol>', + '<ol><ol><li>foo</li></ol></ol>' + ); + } + + function testNestedUl() { + $this->assertResult( + '<ul><ul><li>foo</li></ul></ul>', + '<ul><ul><li>foo</li></ul></ul>' + ); + } + + function testNestedOlWithStrangeEnding() { + $this->assertResult( + '<ol><li><ol><ol><li>foo</li></ol></li><li>foo</li></ol>', + '<ol><li><ol><ol><li>foo</li></ol></ol></li><li>foo</li></ol>' + ); + } + + function testNoAutocloseIfNoParentsCanAccomodateTag() { + $this->assertResult( + '<table><tr><td><li>foo</li></td></tr></table>', + '<table><tr><td>foo</td></tr></table>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php new file mode 100644 index 000000000..e825e2e2a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php @@ -0,0 +1,62 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormed_ErrorsTest extends HTMLPurifier_Strategy_ErrorsHarness +{ + + protected function getStrategy() { + return new HTMLPurifier_Strategy_MakeWellFormed(); + } + + function testUnnecessaryEndTagRemoved() { + $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 0)); + $this->invoke('</b>'); + } + + function testUnnecessaryEndTagToText() { + $this->config->set('Core.EscapeInvalidTags', true); + $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 0)); + $this->invoke('</b>'); + } + + function testTagAutoclose() { + $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', new HTMLPurifier_Token_Start('p', array(), 1, 0)); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array(), 1, 6)); + $this->invoke('<p>Foo<div>Bar</div>'); + } + + function testTagCarryOver() { + $b = new HTMLPurifier_Token_Start('b', array(), 1, 0); + $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $b); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array(), 1, 6)); + $this->invoke('<b>Foo<div>Bar</div>'); + } + + function testStrayEndTagRemoved() { + $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 3)); + $this->invoke('<i></b></i>'); + } + + function testStrayEndTagToText() { + $this->config->set('Core.EscapeInvalidTags', true); + $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 3)); + $this->invoke('<i></b></i>'); + } + + function testTagClosedByElementEnd() { + $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', new HTMLPurifier_Token_Start('b', array(), 1, 3)); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('i', array(), 1, 12)); + $this->invoke('<i><b>Foobar</i>'); + } + + function testTagClosedByDocumentEnd() { + $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', new HTMLPurifier_Token_Start('b', array(), 1, 0)); + $this->invoke('<b>Foobar'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php new file mode 100644 index 000000000..f0e01340a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php @@ -0,0 +1,149 @@ +<?php + +class HTMLPurifier_Strategy_MakeWellFormed_InjectorTest extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->config->set('AutoFormat.AutoParagraph', true); + $this->config->set('AutoFormat.Linkify', true); + $this->config->set('AutoFormat.RemoveEmpty', true); + generate_mock_once('HTMLPurifier_Injector'); + } + + function testEndHandler() { + $mock = new HTMLPurifier_InjectorMock(); + $b = new HTMLPurifier_Token_End('b'); + $b->skip = array(0 => true); + $b->start = new HTMLPurifier_Token_Start('b'); + $b->start->skip = array(0 => true, 1 => true); + $mock->expectAt(0, 'handleEnd', array($b)); + $i = new HTMLPurifier_Token_End('i'); + $i->start = new HTMLPurifier_Token_Start('i'); + $i->skip = array(0 => true); + $i->start->skip = array(0 => true, 1 => true); + $mock->expectAt(1, 'handleEnd', array($i)); + $mock->expectCallCount('handleEnd', 2); + $mock->setReturnValue('getRewind', false); + $this->config->set('AutoFormat.AutoParagraph', false); + $this->config->set('AutoFormat.Linkify', false); + $this->config->set('AutoFormat.Custom', array($mock)); + $this->assertResult('<i><b>asdf</b>', '<i><b>asdf</b></i>'); + } + + function testErrorRequiredElementNotAllowed() { + $this->config->set('HTML.Allowed', ''); + $this->expectError('Cannot enable AutoParagraph injector because p is not allowed'); + $this->expectError('Cannot enable Linkify injector because a is not allowed'); + $this->assertResult('Foobar'); + } + + function testErrorRequiredAttributeNotAllowed() { + $this->config->set('HTML.Allowed', 'a,p'); + $this->expectError('Cannot enable Linkify injector because a.href is not allowed'); + $this->assertResult('<p>http://example.com</p>'); + } + + function testOnlyAutoParagraph() { + $this->assertResult( + 'Foobar', + '<p>Foobar</p>' + ); + } + + function testParagraphWrappingOnlyLink() { + $this->assertResult( + 'http://example.com', + '<p><a href="http://example.com">http://example.com</a></p>' + ); + } + + function testParagraphWrappingNodeContainingLink() { + $this->assertResult( + '<b>http://example.com</b>', + '<p><b><a href="http://example.com">http://example.com</a></b></p>' + ); + } + + function testParagraphWrappingPoorlyFormedNodeContainingLink() { + $this->assertResult( + '<b>http://example.com', + '<p><b><a href="http://example.com">http://example.com</a></b></p>' + ); + } + + function testTwoParagraphsContainingOnlyOneLink() { + $this->assertResult( + "http://example.com\n\nhttp://dev.example.com", +'<p><a href="http://example.com">http://example.com</a></p> + +<p><a href="http://dev.example.com">http://dev.example.com</a></p>' + ); + } + + function testParagraphNextToDivWithLinks() { + $this->assertResult( + 'http://example.com <div>http://example.com</div>', +'<p><a href="http://example.com">http://example.com</a> </p> + +<div><a href="http://example.com">http://example.com</a></div>' + ); + } + + function testRealisticLinkInSentence() { + $this->assertResult( + 'This URL http://example.com is what you need', + '<p>This URL <a href="http://example.com">http://example.com</a> is what you need</p>' + ); + } + + function testParagraphAfterLinkifiedURL() { + $this->assertResult( +"http://google.com + +<b>b</b>", +"<p><a href=\"http://google.com\">http://google.com</a></p> + +<p><b>b</b></p>" + ); + } + + function testEmptyAndParagraph() { + // This is a fairly degenerate case, but it demonstrates that + // the two don't error out together, at least. + // Change this behavior! + $this->assertResult( +"<p>asdf + +asdf<b></b></p> + +<p></p><i></i>", +"<p>asdf</p> + +<p>asdf</p> + +" + ); + } + + function testRewindAndParagraph() { + $this->assertResult( +"bar + +<p><i></i> + +</p> + +foo", +"<p>bar</p> + + + +<p>foo</p>" + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php new file mode 100644 index 000000000..b3ca1646a --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php @@ -0,0 +1,115 @@ +<?php + +class HTMLPurifier_Strategy_RemoveForeignElementsTest extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_RemoveForeignElements(); + } + + function testBlankInput() { + $this->assertResult(''); + } + + function testPreserveRecognizedElements() { + $this->assertResult('This is <b>bold text</b>.'); + } + + function testRemoveForeignElements() { + $this->assertResult( + '<asdf>Bling</asdf><d href="bang">Bong</d><foobar />', + 'BlingBong' + ); + } + + function testRemoveScriptAndContents() { + $this->assertResult( + '<script>alert();</script>', + '' + ); + } + + function testRemoveStyleAndContents() { + $this->assertResult( + '<style>.foo {blink;}</style>', + '' + ); + } + + function testRemoveOnlyScriptTagsLegacy() { + $this->config->set('Core.RemoveScriptContents', false); + $this->assertResult( + '<script>alert();</script>', + 'alert();' + ); + } + + function testRemoveOnlyScriptTags() { + $this->config->set('Core.HiddenElements', array()); + $this->assertResult( + '<script>alert();</script>', + 'alert();' + ); + } + + function testRemoveInvalidImg() { + $this->assertResult('<img />', ''); + } + + function testPreserveValidImg() { + $this->assertResult('<img src="foobar.gif" alt="foobar.gif" />'); + } + + function testPreserveInvalidImgWhenRemovalIsDisabled() { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult('<img />'); + } + + function testTextifyCommentedScriptContents() { + $this->config->set('HTML.Trusted', true); + $this->config->set('Output.CommentScriptContents', false); // simplify output + $this->assertResult( +'<script type="text/javascript"><!-- +alert(<b>bold</b>); +// --></script>', +'<script type="text/javascript"> +alert(<b>bold</b>); +// </script>' + ); + } + + function testRequiredAttributesTestNotPerformedOnEndTag() { + $def = $this->config->getHTMLDefinition(true); + $def->addElement('f', 'Block', 'Optional: #PCDATA', false, array('req*' => 'Text')); + $this->assertResult('<f req="text">Foo</f> Bar'); + } + + function testPreserveCommentsWithHTMLTrusted() { + $this->config->set('HTML.Trusted', true); + $this->assertResult('<!-- foo -->'); + } + + function testRemoveTrailingHyphensInComment() { + $this->config->set('HTML.Trusted', true); + $this->assertResult('<!-- foo ----->', '<!-- foo -->'); + } + + function testCollapseDoubleHyphensInComment() { + $this->config->set('HTML.Trusted', true); + $this->assertResult('<!-- bo --- asdf--as -->', '<!-- bo - asdf-as -->'); + } + + function testPreserveCommentsWithLookup() { + $this->config->set('HTML.AllowedComments', array('allowed')); + $this->assertResult('<!-- allowed --><!-- not allowed -->', '<!-- allowed -->'); + } + + function testPreserveCommentsWithRegexp() { + $this->config->set('HTML.AllowedCommentsRegexp', '/^allowed[1-9]$/'); + $this->assertResult('<!-- allowed1 --><!-- not allowed -->', '<!-- allowed1 -->'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php new file mode 100644 index 000000000..4b4e31074 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php @@ -0,0 +1,71 @@ +<?php + +class HTMLPurifier_Strategy_RemoveForeignElements_ErrorsTest extends HTMLPurifier_Strategy_ErrorsHarness +{ + + public function setup() { + parent::setup(); + $this->config->set('HTML.TidyLevel', 'heavy'); + } + + protected function getStrategy() { + return new HTMLPurifier_Strategy_RemoveForeignElements(); + } + + function testTagTransform() { + $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', 'center'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array('style' => 'text-align:center;'), 1)); + $this->invoke('<center>'); + } + + function testMissingRequiredAttr() { + // a little fragile, since img has two required attributes + $this->expectErrorCollection(E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', 'alt'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Empty('img', array(), 1)); + $this->invoke('<img />'); + } + + function testForeignElementToText() { + $this->config->set('Core.EscapeInvalidTags', true); + $this->expectErrorCollection(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('invalid', array(), 1)); + $this->invoke('<invalid>'); + } + + function testForeignElementRemoved() { + // uses $CurrentToken.Serialized + $this->expectErrorCollection(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('invalid', array(), 1)); + $this->invoke('<invalid>'); + } + + function testCommentRemoved() { + $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test ', 1)); + $this->invoke('<!-- test -->'); + } + + function testTrailingHyphenInCommentRemoved() { + $this->config->set('HTML.Trusted', true); + $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test ', 1)); + $this->invoke('<!-- test ---->'); + } + + function testDoubleHyphenInCommentRemoved() { + $this->config->set('HTML.Trusted', true); + $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test - test - test ', 1)); + $this->invoke('<!-- test --- test -- test -->'); + } + + function testForeignMetaElementRemoved() { + $this->collector->expectAt(0, 'send', array(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed')); + $this->collector->expectContextAt(0, 'CurrentToken', new HTMLPurifier_Token_Start('script', array(), 1)); + $this->collector->expectAt(1, 'send', array(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', 'script')); + $this->invoke('<script>asdf'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_TidyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_TidyTest.php new file mode 100644 index 000000000..22fc456a7 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_TidyTest.php @@ -0,0 +1,45 @@ +<?php + +class HTMLPurifier_Strategy_RemoveForeignElements_TidyTest + extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_RemoveForeignElements(); + $this->config->set('HTML.TidyLevel', 'heavy'); + } + + function testCenterTransform() { + $this->assertResult( + '<center>Look I am Centered!</center>', + '<div style="text-align:center;">Look I am Centered!</div>' + ); + } + + function testFontTransform() { + $this->assertResult( + '<font color="red" face="Arial" size="6">Big Warning!</font>', + '<span style="color:red;font-family:Arial;font-size:xx-large;">Big'. + ' Warning!</span>' + ); + } + + function testTransformToForbiddenElement() { + $this->config->set('HTML.Allowed', 'div'); + $this->assertResult( + '<font color="red" face="Arial" size="6">Big Warning!</font>', + 'Big Warning!' + ); + } + + function testMenuTransform() { + $this->assertResult( + '<menu><li>Item 1</li></menu>', + '<ul><li>Item 1</li></ul>' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributesTest.php new file mode 100644 index 000000000..5fc86cbda --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributesTest.php @@ -0,0 +1,232 @@ +<?php + +class HTMLPurifier_Strategy_ValidateAttributesTest extends + HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_ValidateAttributes(); + } + + function testEmptyInput() { + $this->assertResult(''); + } + + function testRemoveIDByDefault() { + $this->assertResult( + '<div id="valid">Kill the ID.</div>', + '<div>Kill the ID.</div>' + ); + } + + function testRemoveInvalidDir() { + $this->assertResult( + '<span dir="up-to-down">Bad dir.</span>', + '<span>Bad dir.</span>' + ); + } + + function testPreserveValidClass() { + $this->assertResult('<div class="valid">Valid</div>'); + } + + function testSelectivelyRemoveInvalidClasses() { + $this->config->set('HTML.Doctype', 'XHTML 1.1'); + $this->assertResult( + '<div class="valid 0invalid">Keep valid.</div>', + '<div class="valid">Keep valid.</div>' + ); + } + + function testPreserveTitle() { + $this->assertResult( + '<acronym title="PHP: Hypertext Preprocessor">PHP</acronym>' + ); + } + + function testAddXMLLang() { + $this->assertResult( + '<span lang="fr">La soupe.</span>', + '<span lang="fr" xml:lang="fr">La soupe.</span>' + ); + } + + function testOnlyXMLLangInXHTML11() { + $this->config->set('HTML.Doctype', 'XHTML 1.1'); + $this->assertResult( + '<b lang="en">asdf</b>', + '<b xml:lang="en">asdf</b>' + ); + } + + function testBasicURI() { + $this->assertResult('<a href="http://www.google.com/">Google</a>'); + } + + function testInvalidURI() { + $this->assertResult( + '<a href="javascript:badstuff();">Google</a>', + '<a>Google</a>' + ); + } + + function testBdoAddMissingDir() { + $this->assertResult( + '<bdo>Go left.</bdo>', + '<bdo dir="ltr">Go left.</bdo>' + ); + } + + function testBdoReplaceInvalidDirWithDefault() { + $this->assertResult( + '<bdo dir="blahblah">Invalid value!</bdo>', + '<bdo dir="ltr">Invalid value!</bdo>' + ); + } + + function testBdoAlternateDefaultDir() { + $this->config->set('Attr.DefaultTextDir', 'rtl'); + $this->assertResult( + '<bdo>Go right.</bdo>', + '<bdo dir="rtl">Go right.</bdo>' + ); + } + + function testRemoveDirWhenNotRequired() { + $this->assertResult( + '<span dir="blahblah">Invalid value!</span>', + '<span>Invalid value!</span>' + ); + } + + function testTableAttributes() { + $this->assertResult( +'<table frame="above" rules="rows" summary="A test table" border="2" cellpadding="5%" cellspacing="3" width="100%"> + <col align="right" width="4*" /> + <col charoff="5" align="char" width="*" /> + <tr valign="top"> + <th abbr="name">Fiddly name</th> + <th abbr="price">Super-duper-price</th> + </tr> + <tr> + <td abbr="carrot">Carrot Humungous</td> + <td>$500.23</td> + </tr> + <tr> + <td colspan="2">Taken off the market</td> + </tr> +</table>' + ); + } + + function testColSpanIsNonZero() { + $this->assertResult( + '<col span="0" />', + '<col />' + ); + } + + function testImgAddDefaults() { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + '<img />', + '<img src="" alt="Invalid image" />' + ); + } + + function testImgGenerateAlt() { + $this->assertResult( + '<img src="foobar.jpg" />', + '<img src="foobar.jpg" alt="foobar.jpg" />' + ); + } + + function testImgAddDefaultSrc() { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + '<img alt="pretty picture" />', + '<img alt="pretty picture" src="" />' + ); + } + + function testImgRemoveNonRetrievableProtocol() { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + '<img src="mailto:foo@example.com" />', + '<img alt="mailto:foo@example.com" src="" />' + ); + } + + function testPreserveRel() { + $this->config->set('Attr.AllowedRel', 'nofollow'); + $this->assertResult('<a href="foo" rel="nofollow" />'); + } + + function testPreserveTarget() { + $this->config->set('Attr.AllowedFrameTargets', '_top'); + $this->config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); + $this->assertResult('<a href="foo" target="_top" />'); + } + + function testRemoveTargetWhenNotSupported() { + $this->config->set('HTML.Doctype', 'XHTML 1.0 Strict'); + $this->config->set('Attr.AllowedFrameTargets', '_top'); + $this->assertResult( + '<a href="foo" target="_top" />', + '<a href="foo" />' + ); + } + + function testKeepAbsoluteCSSWidthAndHeightOnImg() { + $this->assertResult( + '<img src="" alt="" style="width:10px;height:10px;border:1px solid #000;" />' + ); + } + + function testRemoveLargeCSSWidthAndHeightOnImg() { + $this->assertResult( + '<img src="" alt="" style="width:10000000px;height:10000000px;border:1px solid #000;" />', + '<img src="" alt="" style="border:1px solid #000;" />' + ); + } + + function testRemoveLargeCSSWidthAndHeightOnImgWithUserConf() { + $this->config->set('CSS.MaxImgLength', '1px'); + $this->assertResult( + '<img src="" alt="" style="width:1mm;height:1mm;border:1px solid #000;" />', + '<img src="" alt="" style="border:1px solid #000;" />' + ); + } + + function testKeepLargeCSSWidthAndHeightOnImgWhenToldTo() { + $this->config->set('CSS.MaxImgLength', null); + $this->assertResult( + '<img src="" alt="" style="width:10000000px;height:10000000px;border:1px solid #000;" />' + ); + } + + function testKeepPercentCSSWidthAndHeightOnImgWhenToldTo() { + $this->config->set('CSS.MaxImgLength', null); + $this->assertResult( + '<img src="" alt="" style="width:100%;height:100%;border:1px solid #000;" />' + ); + } + + function testRemoveRelativeCSSWidthAndHeightOnImg() { + $this->assertResult( + '<img src="" alt="" style="width:10em;height:10em;border:1px solid #000;" />', + '<img src="" alt="" style="border:1px solid #000;" />' + ); + } + + function testRemovePercentCSSWidthAndHeightOnImg() { + $this->assertResult( + '<img src="" alt="" style="width:100%;height:100%;border:1px solid #000;" />', + '<img src="" alt="" style="border:1px solid #000;" />' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_IDTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_IDTest.php new file mode 100644 index 000000000..c8c750723 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_IDTest.php @@ -0,0 +1,63 @@ +<?php + +class HTMLPurifier_Strategy_ValidateAttributes_IDTest extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_ValidateAttributes(); + $this->config->set('Attr.EnableID', true); + } + + + function testPreserveIDWhenEnabled() { + $this->assertResult('<div id="valid">Preserve the ID.</div>'); + } + + function testRemoveInvalidID() { + $this->assertResult( + '<div id="0invalid">Kill the ID.</div>', + '<div>Kill the ID.</div>' + ); + } + + function testRemoveDuplicateID() { + $this->assertResult( + '<div id="valid">Valid</div><div id="valid">Invalid</div>', + '<div id="valid">Valid</div><div>Invalid</div>' + ); + } + + function testAttributeKeyCaseInsensitivity() { + $this->assertResult( + '<div ID="valid">Convert ID to lowercase.</div>', + '<div id="valid">Convert ID to lowercase.</div>' + ); + } + + function testTrimWhitespace() { + $this->assertResult( + '<div id=" valid ">Trim whitespace.</div>', + '<div id="valid">Trim whitespace.</div>' + ); + } + + function testIDBlacklist() { + $this->config->set('Attr.IDBlacklist', array('invalid')); + $this->assertResult( + '<div id="invalid">Invalid</div>', + '<div>Invalid</div>' + ); + } + + function testNameConvertedToID() { + $this->config->set('HTML.TidyLevel', 'heavy'); + $this->assertResult( + '<a name="foobar" />', + '<a id="foobar" />' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_TidyTest.php b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_TidyTest.php new file mode 100644 index 000000000..d4eeeebfc --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/Strategy/ValidateAttributes_TidyTest.php @@ -0,0 +1,351 @@ +<?php + +class HTMLPurifier_Strategy_ValidateAttributes_TidyTest extends HTMLPurifier_StrategyHarness +{ + + function setUp() { + parent::setUp(); + $this->obj = new HTMLPurifier_Strategy_ValidateAttributes(); + $this->config->set('HTML.TidyLevel', 'heavy'); + } + + function testConvertCenterAlign() { + $this->assertResult( + '<h1 align="center">Centered Headline</h1>', + '<h1 style="text-align:center;">Centered Headline</h1>' + ); + } + + function testConvertRightAlign() { + $this->assertResult( + '<h1 align="right">Right-aligned Headline</h1>', + '<h1 style="text-align:right;">Right-aligned Headline</h1>' + ); + } + + function testConvertLeftAlign() { + $this->assertResult( + '<h1 align="left">Left-aligned Headline</h1>', + '<h1 style="text-align:left;">Left-aligned Headline</h1>' + ); + } + + function testConvertJustifyAlign() { + $this->assertResult( + '<p align="justify">Justified Paragraph</p>', + '<p style="text-align:justify;">Justified Paragraph</p>' + ); + } + + function testRemoveInvalidAlign() { + $this->assertResult( + '<h1 align="invalid">Invalid Headline</h1>', + '<h1>Invalid Headline</h1>' + ); + } + + function testConvertTableLengths() { + $this->assertResult( + '<td width="5%" height="10" /><th width="10" height="5%" /><hr width="10" height="10" />', + '<td style="width:5%;height:10px;" /><th style="width:10px;height:5%;" /><hr style="width:10px;" />' + ); + } + + function testTdConvertNowrap() { + $this->assertResult( + '<td nowrap />', + '<td style="white-space:nowrap;" />' + ); + } + + function testCaptionConvertAlignLeft() { + $this->assertResult( + '<caption align="left" />', + '<caption style="text-align:left;" />' + ); + } + + function testCaptionConvertAlignRight() { + $this->assertResult( + '<caption align="right" />', + '<caption style="text-align:right;" />' + ); + } + + function testCaptionConvertAlignTop() { + $this->assertResult( + '<caption align="top" />', + '<caption style="caption-side:top;" />' + ); + } + + function testCaptionConvertAlignBottom() { + $this->assertResult( + '<caption align="bottom" />', + '<caption style="caption-side:bottom;" />' + ); + } + + function testCaptionRemoveInvalidAlign() { + $this->assertResult( + '<caption align="nonsense" />', + '<caption />' + ); + } + + function testTableConvertAlignLeft() { + $this->assertResult( + '<table align="left" />', + '<table style="float:left;" />' + ); + } + + function testTableConvertAlignCenter() { + $this->assertResult( + '<table align="center" />', + '<table style="margin-left:auto;margin-right:auto;" />' + ); + } + + function testTableConvertAlignRight() { + $this->assertResult( + '<table align="right" />', + '<table style="float:right;" />' + ); + } + + function testTableRemoveInvalidAlign() { + $this->assertResult( + '<table align="top" />', + '<table />' + ); + } + + function testImgConvertAlignLeft() { + $this->assertResult( + '<img src="foobar.jpg" alt="foobar" align="left" />', + '<img src="foobar.jpg" alt="foobar" style="float:left;" />' + ); + } + + function testImgConvertAlignRight() { + $this->assertResult( + '<img src="foobar.jpg" alt="foobar" align="right" />', + '<img src="foobar.jpg" alt="foobar" style="float:right;" />' + ); + } + + function testImgConvertAlignBottom() { + $this->assertResult( + '<img src="foobar.jpg" alt="foobar" align="bottom" />', + '<img src="foobar.jpg" alt="foobar" style="vertical-align:baseline;" />' + ); + } + + function testImgConvertAlignMiddle() { + $this->assertResult( + '<img src="foobar.jpg" alt="foobar" align="middle" />', + '<img src="foobar.jpg" alt="foobar" style="vertical-align:middle;" />' + ); + } + + function testImgConvertAlignTop() { + $this->assertResult( + '<img src="foobar.jpg" alt="foobar" align="top" />', + '<img src="foobar.jpg" alt="foobar" style="vertical-align:top;" />' + ); + } + + function testImgRemoveInvalidAlign() { + $this->assertResult( + '<img src="foobar.jpg" alt="foobar" align="outerspace" />', + '<img src="foobar.jpg" alt="foobar" />' + ); + } + + function testBorderConvertHVSpace() { + $this->assertResult( + '<img src="foo" alt="foo" hspace="1" vspace="3" />', + '<img src="foo" alt="foo" style="margin-top:3px;margin-bottom:3px;margin-left:1px;margin-right:1px;" />' + ); + } + + function testHrConvertSize() { + $this->assertResult( + '<hr size="3" />', + '<hr style="height:3px;" />' + ); + } + + function testHrConvertNoshade() { + $this->assertResult( + '<hr noshade />', + '<hr style="color:#808080;background-color:#808080;border:0;" />' + ); + } + + function testHrConvertAlignLeft() { + $this->assertResult( + '<hr align="left" />', + '<hr style="margin-left:0;margin-right:auto;text-align:left;" />' + ); + } + + function testHrConvertAlignCenter() { + $this->assertResult( + '<hr align="center" />', + '<hr style="margin-left:auto;margin-right:auto;text-align:center;" />' + ); + } + + function testHrConvertAlignRight() { + $this->assertResult( + '<hr align="right" />', + '<hr style="margin-left:auto;margin-right:0;text-align:right;" />' + ); + } + + function testHrRemoveInvalidAlign() { + $this->assertResult( + '<hr align="bottom" />', + '<hr />' + ); + } + + function testBrConvertClearLeft() { + $this->assertResult( + '<br clear="left" />', + '<br style="clear:left;" />' + ); + } + + function testBrConvertClearRight() { + $this->assertResult( + '<br clear="right" />', + '<br style="clear:right;" />' + ); + } + + function testBrConvertClearAll() { + $this->assertResult( + '<br clear="all" />', + '<br style="clear:both;" />' + ); + } + + function testBrConvertClearNone() { + $this->assertResult( + '<br clear="none" />', + '<br style="clear:none;" />' + ); + } + + function testBrRemoveInvalidClear() { + $this->assertResult( + '<br clear="foo" />', + '<br />' + ); + } + + function testUlConvertTypeDisc() { + $this->assertResult( + '<ul type="disc" />', + '<ul style="list-style-type:disc;" />' + ); + } + + function testUlConvertTypeSquare() { + $this->assertResult( + '<ul type="square" />', + '<ul style="list-style-type:square;" />' + ); + } + + function testUlConvertTypeCircle() { + $this->assertResult( + '<ul type="circle" />', + '<ul style="list-style-type:circle;" />' + ); + } + + function testUlConvertTypeCaseInsensitive() { + $this->assertResult( + '<ul type="CIRCLE" />', + '<ul style="list-style-type:circle;" />' + ); + } + + function testUlRemoveInvalidType() { + $this->assertResult( + '<ul type="a" />', + '<ul />' + ); + } + + function testOlConvertType1() { + $this->assertResult( + '<ol type="1" />', + '<ol style="list-style-type:decimal;" />' + ); + } + + function testOlConvertTypeLowerI() { + $this->assertResult( + '<ol type="i" />', + '<ol style="list-style-type:lower-roman;" />' + ); + } + + function testOlConvertTypeUpperI() { + $this->assertResult( + '<ol type="I" />', + '<ol style="list-style-type:upper-roman;" />' + ); + } + + function testOlConvertTypeLowerA() { + $this->assertResult( + '<ol type="a" />', + '<ol style="list-style-type:lower-alpha;" />' + ); + } + + function testOlConvertTypeUpperA() { + $this->assertResult( + '<ol type="A" />', + '<ol style="list-style-type:upper-alpha;" />' + ); + } + + function testOlRemoveInvalidType() { + $this->assertResult( + '<ol type="disc" />', + '<ol />' + ); + } + + function testLiConvertTypeCircle() { + $this->assertResult( + '<li type="circle" />', + '<li style="list-style-type:circle;" />' + ); + } + + function testLiConvertTypeA() { + $this->assertResult( + '<li type="A" />', + '<li style="list-style-type:upper-alpha;" />' + ); + } + + function testLiConvertTypeCaseSensitive() { + $this->assertResult( + '<li type="CIRCLE" />', + '<li />' + ); + } + + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StrategyHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/StrategyHarness.php new file mode 100644 index 000000000..15f22f6bd --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/StrategyHarness.php @@ -0,0 +1,15 @@ +<?php + +class HTMLPurifier_StrategyHarness extends HTMLPurifier_ComplexHarness +{ + + function setUp() { + parent::setUp(); + $this->func = 'execute'; + $this->to_tokens = true; + $this->to_html = true; + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/AppendMultiline.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/AppendMultiline.txt new file mode 100644 index 000000000..dfc12da3b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/AppendMultiline.txt @@ -0,0 +1,5 @@ +--KEY-- +Line1 +--KEY-- +Line2 +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Default.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Default.txt new file mode 100644 index 000000000..9d2aea80d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Default.txt @@ -0,0 +1,2 @@ +DefaultValue +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Multi.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Multi.txt new file mode 100644 index 000000000..caea55b1e --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Multi.txt @@ -0,0 +1,19 @@ +Namespace.Directive +TYPE: string +CHAIN-ME: 2 +--DESCRIPTION-- +Multiline +stuff +--FOR-WHO-- +Single multiline +---- + +Namespace.Directive2 +TYPE: integer +CHAIN-ME: 3 +--DESCRIPTION-- +M +stuff +--FOR-WHO-- +Single multiline2 +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/OverrideSingle.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/OverrideSingle.txt new file mode 100644 index 000000000..1d27c7148 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/OverrideSingle.txt @@ -0,0 +1,3 @@ +KEY: Original +KEY: New +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Simple.txt b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Simple.txt new file mode 100644 index 000000000..e8042d9ed --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParser/Simple.txt @@ -0,0 +1,10 @@ +Namespace.Directive +TYPE: string +CHAIN-ME: 2 +--DESCRIPTION-- +Multiline +stuff +--EMPTY-- +--FOR-WHO-- +Single multiline +--# vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashParserTest.php b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParserTest.php new file mode 100644 index 000000000..6d357f86c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashParserTest.php @@ -0,0 +1,89 @@ +<?php + +/** + * @note Sample input files are located in the StringHashParser/ directory. + */ +class HTMLPurifier_StringHashParserTest extends UnitTestCase +{ + + /** + * Instance of ConfigSchema_StringHashParser being tested. + */ + protected $parser; + + public function setup() { + $this->parser = new HTMLPurifier_StringHashParser(); + } + + /** + * Assert that $file gets parsed into the form of $expect + */ + protected function assertParse($file, $expect) { + $result = $this->parser->parseFile(dirname(__FILE__) . '/StringHashParser/' . $file); + $this->assertIdentical($result, $expect); + } + + function testSimple() { + $this->assertParse('Simple.txt', array( + 'ID' => 'Namespace.Directive', + 'TYPE' => 'string', + 'CHAIN-ME' => '2', + 'DESCRIPTION' => "Multiline\nstuff\n", + 'EMPTY' => '', + 'FOR-WHO' => "Single multiline\n", + )); + } + + function testOverrideSingle() { + $this->assertParse('OverrideSingle.txt', array( + 'KEY' => 'New', + )); + } + + function testAppendMultiline() { + $this->assertParse('AppendMultiline.txt', array( + 'KEY' => "Line1\nLine2\n", + )); + } + + function testDefault() { + $this->parser->default = 'NEW-ID'; + $this->assertParse('Default.txt', array( + 'NEW-ID' => 'DefaultValue', + )); + } + + function testError() { + try { + $this->parser->parseFile('NoExist.txt'); + } catch (HTMLPurifier_ConfigSchema_Exception $e) { + $this->assertIdentical($e->getMessage(), 'File NoExist.txt does not exist'); + } + } + + function testParseMultiple() { + $result = $this->parser->parseMultiFile(dirname(__FILE__) . '/StringHashParser/Multi.txt'); + $this->assertIdentical( + $result, + array( + array( + 'ID' => 'Namespace.Directive', + 'TYPE' => 'string', + 'CHAIN-ME' => '2', + 'DESCRIPTION' => "Multiline\nstuff\n", + 'FOR-WHO' => "Single multiline\n", + ), + array( + 'ID' => 'Namespace.Directive2', + 'TYPE' => 'integer', + 'CHAIN-ME' => '3', + 'DESCRIPTION' => "M\nstuff\n", + 'FOR-WHO' => "Single multiline2\n", + ) + ) + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/StringHashTest.php b/lib/htmlpurifier/tests/HTMLPurifier/StringHashTest.php new file mode 100644 index 000000000..653e5bf70 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/StringHashTest.php @@ -0,0 +1,20 @@ +<?php + +class HTMLPurifier_StringHashTest extends UnitTestCase +{ + + function testUsed() { + $hash = new HTMLPurifier_StringHash(array( + 'key' => 'value', + 'key2' => 'value2' + )); + $this->assertIdentical($hash->getAccessed(), array()); + $t = $hash->offsetGet('key'); + $this->assertIdentical($hash->getAccessed(), array('key' => true)); + $hash->resetAccessed(); + $this->assertIdentical($hash->getAccessed(), array()); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/TagTransformTest.php b/lib/htmlpurifier/tests/HTMLPurifier/TagTransformTest.php new file mode 100644 index 000000000..8669d5b91 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/TagTransformTest.php @@ -0,0 +1,179 @@ +<?php + +// needs to be seperated into files +class HTMLPurifier_TagTransformTest extends HTMLPurifier_Harness +{ + + /** + * Asserts that a transformation happens + * + * This assertion performs several tests on the transform: + * + * -# Transforms a start tag with only $name and no attributes + * -# Transforms a start tag with $name and $attributes + * -# Transform an end tag + * -# Transform an empty tag with only $name and no attributes + * -# Transform an empty tag with $name and $attributes + * + * In its current form, it assumes that start and empty tags would be + * treated the same, and is really ensuring that the tag transform doesn't + * do anything wonky to the tag type. + * + * @param $transformer HTMLPurifier_TagTransform class to test + * @param $name Name of the original tag + * @param $attributes Attributes of the original tag + * @param $expect_name Name of output tag + * @param $expect_attributes Attributes of output tag when $attributes + * is included. + * @param $expect_added_attributes Attributes of output tag when $attributes + * are omitted. + * @param $config_array Configuration array for HTMLPurifier_Config + * @param $context_array Context array for HTMLPurifier_Context + */ + protected function assertTransformation($transformer, + $name, $attributes, + $expect_name, $expect_attributes, + $expect_added_attributes = array(), + $config_array = array(), $context_array = array()) { + + $config = HTMLPurifier_Config::createDefault(); + $config->loadArray($config_array); + + $context = new HTMLPurifier_Context(); + $context->loadArray($context_array); + + // start tag transform + $this->assertIdentical( + new HTMLPurifier_Token_Start($expect_name, $expect_added_attributes), + $transformer->transform( + new HTMLPurifier_Token_Start($name), $config, $context) + ); + + // start tag transform with attributes + $this->assertIdentical( + new HTMLPurifier_Token_Start($expect_name, $expect_attributes), + $transformer->transform( + new HTMLPurifier_Token_Start($name, $attributes), + $config, $context + ) + ); + + // end tag transform + $this->assertIdentical( + new HTMLPurifier_Token_End($expect_name), + $transformer->transform( + new HTMLPurifier_Token_End($name), $config, $context + ) + ); + + // empty tag transform + $this->assertIdentical( + new HTMLPurifier_Token_Empty($expect_name, $expect_added_attributes), + $transformer->transform( + new HTMLPurifier_Token_Empty($name), $config, $context + ) + ); + + // empty tag transform with attributes + $this->assertIdentical( + new HTMLPurifier_Token_Empty($expect_name, $expect_attributes), + $transformer->transform( + new HTMLPurifier_Token_Empty($name, $attributes), + $config, $context + ) + ); + + + } + + function testSimple() { + + $transformer = new HTMLPurifier_TagTransform_Simple('ul'); + + $this->assertTransformation( + $transformer, + 'menu', array('class' => 'boom'), + 'ul', array('class' => 'boom') + ); + + } + + function testSimpleWithCSS() { + + $transformer = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;'); + + $this->assertTransformation( + $transformer, + 'center', array('class' => 'boom', 'style'=>'font-weight:bold;'), + 'div', array('class' => 'boom', 'style'=>'text-align:center;font-weight:bold;'), + array('style'=>'text-align:center;') + ); + + // test special case, uppercase attribute key + $this->assertTransformation( + $transformer, + 'center', array('STYLE'=>'font-weight:bold;'), + 'div', array('style'=>'text-align:center;font-weight:bold;'), + array('style'=>'text-align:center;') + ); + + } + + protected function assertSizeToStyle($transformer, $size, $style) { + $this->assertTransformation( + $transformer, + 'font', array('size' => $size), + 'span', array('style' => 'font-size:' . $style . ';') + ); + } + + function testFont() { + + $transformer = new HTMLPurifier_TagTransform_Font(); + + // test a font-face transformation + $this->assertTransformation( + $transformer, + 'font', array('face' => 'Arial'), + 'span', array('style' => 'font-family:Arial;') + ); + + // test a color transformation + $this->assertTransformation( + $transformer, + 'font', array('color' => 'red'), + 'span', array('style' => 'color:red;') + ); + + // test the size transforms + $this->assertSizeToStyle($transformer, '0', 'xx-small'); + $this->assertSizeToStyle($transformer, '1', 'xx-small'); + $this->assertSizeToStyle($transformer, '2', 'small'); + $this->assertSizeToStyle($transformer, '3', 'medium'); + $this->assertSizeToStyle($transformer, '4', 'large'); + $this->assertSizeToStyle($transformer, '5', 'x-large'); + $this->assertSizeToStyle($transformer, '6', 'xx-large'); + $this->assertSizeToStyle($transformer, '7', '300%'); + $this->assertSizeToStyle($transformer, '-1', 'smaller'); + $this->assertSizeToStyle($transformer, '-2', '60%'); + $this->assertSizeToStyle($transformer, '-3', '60%'); + $this->assertSizeToStyle($transformer, '+1', 'larger'); + $this->assertSizeToStyle($transformer, '+2', '150%'); + $this->assertSizeToStyle($transformer, '+3', '200%'); + $this->assertSizeToStyle($transformer, '+4', '300%'); + $this->assertSizeToStyle($transformer, '+5', '300%'); + $this->assertTransformation( + $transformer, 'font', array('size' => ''), + 'span', array() + ); + + // test multiple transforms, the alphabetical ordering is important + $this->assertTransformation( + $transformer, + 'font', array('color' => 'red', 'face' => 'Arial', 'size' => '6'), + 'span', array('style' => 'color:red;font-family:Arial;font-size:xx-large;') + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/TokenFactoryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/TokenFactoryTest.php new file mode 100644 index 000000000..3eb731f83 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/TokenFactoryTest.php @@ -0,0 +1,17 @@ +<?php + +class HTMLPurifier_TokenFactoryTest extends HTMLPurifier_Harness +{ + public function test() { + + $factory = new HTMLPurifier_TokenFactory(); + + $regular = new HTMLPurifier_Token_Start('a', array('href' => 'about:blank')); + $generated = $factory->createStart('a', array('href' => 'about:blank')); + + $this->assertIdentical($regular, $generated); + + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/TokenTest.php b/lib/htmlpurifier/tests/HTMLPurifier/TokenTest.php new file mode 100644 index 000000000..099b98603 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/TokenTest.php @@ -0,0 +1,34 @@ +<?php + +class HTMLPurifier_TokenTest extends HTMLPurifier_Harness +{ + + protected function assertTokenConstruction($name, $attr, + $expect_name = null, $expect_attr = null + ) { + if ($expect_name === null) $expect_name = $name; + if ($expect_attr === null) $expect_attr = $attr; + $token = new HTMLPurifier_Token_Start($name, $attr); + + $this->assertIdentical($expect_name, $token->name); + $this->assertIdentical($expect_attr, $token->attr); + } + + function testConstruct() { + + // standard case + $this->assertTokenConstruction('a', array('href' => 'about:blank')); + + // lowercase the tag's name + $this->assertTokenConstruction('A', array('href' => 'about:blank'), + 'a'); + + // lowercase attributes + $this->assertTokenConstruction('a', array('HREF' => 'about:blank'), + 'a', array('href' => 'about:blank')); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIDefinitionTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIDefinitionTest.php new file mode 100644 index 000000000..6ab0c2a6b --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIDefinitionTest.php @@ -0,0 +1,62 @@ +<?php + +class HTMLPurifier_URIDefinitionTest extends HTMLPurifier_URIHarness +{ + + protected function createFilterMock($expect = true, $result = true, $post = false, $setup = true) { + static $i = 0; + generate_mock_once('HTMLPurifier_URIFilter'); + $mock = new HTMLPurifier_URIFilterMock(); + if ($expect) $mock->expectOnce('filter'); + else $mock->expectNever('filter'); + $mock->setReturnValue('filter', $result); + $mock->setReturnValue('prepare', $setup); + $mock->name = $i++; + $mock->post = $post; + return $mock; + } + + function test_filter() { + $def = new HTMLPurifier_URIDefinition(); + $def->addFilter($this->createFilterMock(), $this->config); + $def->addFilter($this->createFilterMock(), $this->config); + $uri = $this->createURI('test'); + $this->assertTrue($def->filter($uri, $this->config, $this->context)); + } + + function test_filter_earlyAbortIfFail() { + $def = new HTMLPurifier_URIDefinition(); + $def->addFilter($this->createFilterMock(true, false), $this->config); + $def->addFilter($this->createFilterMock(false), $this->config); // never called + $uri = $this->createURI('test'); + $this->assertFalse($def->filter($uri, $this->config, $this->context)); + } + + function test_setupMemberVariables_collisionPrecedenceIsHostBaseScheme() { + $this->config->set('URI.Host', $host = 'example.com'); + $this->config->set('URI.Base', $base = 'http://sub.example.com/foo/bar.html'); + $this->config->set('URI.DefaultScheme', 'ftp'); + $def = new HTMLPurifier_URIDefinition(); + $def->setup($this->config); + $this->assertIdentical($def->host, $host); + $this->assertIdentical($def->base, $this->createURI($base)); + $this->assertIdentical($def->defaultScheme, 'http'); // not ftp! + } + + function test_setupMemberVariables_onlyScheme() { + $this->config->set('URI.DefaultScheme', 'ftp'); + $def = new HTMLPurifier_URIDefinition(); + $def->setup($this->config); + $this->assertIdentical($def->defaultScheme, 'ftp'); + } + + function test_setupMemberVariables_onlyBase() { + $this->config->set('URI.Base', 'http://sub.example.com/foo/bar.html'); + $def = new HTMLPurifier_URIDefinition(); + $def->setup($this->config); + $this->assertIdentical($def->host, 'sub.example.com'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalResourcesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalResourcesTest.php new file mode 100644 index 000000000..21c70c145 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalResourcesTest.php @@ -0,0 +1,23 @@ +<?php + +class HTMLPurifier_URIFilter_DisableExternalResourcesTest extends + HTMLPurifier_URIFilter_DisableExternalTest +{ + + function setUp() { + parent::setUp(); + $this->filter = new HTMLPurifier_URIFilter_DisableExternalResources(); + $var = true; + $this->context->register('EmbeddedURI', $var); + } + + function testPreserveWhenNotEmbedded() { + $this->context->destroy('EmbeddedURI'); // undo setUp + $this->assertFiltering( + 'http://example.com' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalTest.php new file mode 100644 index 000000000..3176246a1 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableExternalTest.php @@ -0,0 +1,53 @@ +<?php + +class HTMLPurifier_URIFilter_DisableExternalTest extends HTMLPurifier_URIFilterHarness +{ + + function setUp() { + parent::setUp(); + $this->filter = new HTMLPurifier_URIFilter_DisableExternal(); + } + + function testRemoveExternal() { + $this->assertFiltering( + 'http://example.com', false + ); + } + + function testPreserveInternal() { + $this->assertFiltering( + '/foo/bar' + ); + } + + function testPreserveOurHost() { + $this->config->set('URI.Host', 'example.com'); + $this->assertFiltering( + 'http://example.com' + ); + } + + function testPreserveOurSubdomain() { + $this->config->set('URI.Host', 'example.com'); + $this->assertFiltering( + 'http://www.example.com' + ); + } + + function testRemoveSuperdomain() { + $this->config->set('URI.Host', 'www.example.com'); + $this->assertFiltering( + 'http://example.com', false + ); + } + + function testBaseAsHost() { + $this->config->set('URI.Base', 'http://www.example.com/foo/bar'); + $this->assertFiltering( + 'http://www.example.com/baz' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableResourcesTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableResourcesTest.php new file mode 100644 index 000000000..c2cea8fe6 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/DisableResourcesTest.php @@ -0,0 +1,24 @@ +<?php + +class HTMLPurifier_URIFilter_DisableResourcesTest extends HTMLPurifier_URIFilterHarness +{ + + function setUp() { + parent::setUp(); + $this->filter = new HTMLPurifier_URIFilter_DisableResources(); + $var = true; + $this->context->register('EmbeddedURI', $var); + } + + function testRemoveResource() { + $this->assertFiltering('/foo/bar', false); + } + + function testPreserveRegular() { + $this->context->destroy('EmbeddedURI'); // undo setUp + $this->assertFiltering('/foo/bar'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/HostBlacklistTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/HostBlacklistTest.php new file mode 100644 index 000000000..4af5a6009 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/HostBlacklistTest.php @@ -0,0 +1,29 @@ +<?php + +class HTMLPurifier_URIFilter_HostBlacklistTest extends HTMLPurifier_URIFilterHarness +{ + + function setUp() { + parent::setUp(); + $this->filter = new HTMLPurifier_URIFilter_HostBlacklist(); + } + + function testRejectBlacklistedHost() { + $this->config->set('URI.HostBlacklist', 'example.com'); + $this->assertFiltering('http://example.com', false); + } + + function testRejectBlacklistedHostThoughNotTrue() { + // maybe this behavior should change + $this->config->set('URI.HostBlacklist', 'example.com'); + $this->assertFiltering('http://example.comcast.com', false); + } + + function testPreserveNonBlacklistedHost() { + $this->config->set('URI.HostBlacklist', 'example.com'); + $this->assertFiltering('http://google.com'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MakeAbsoluteTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MakeAbsoluteTest.php new file mode 100644 index 000000000..9002f1ff8 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MakeAbsoluteTest.php @@ -0,0 +1,146 @@ +<?php + +class HTMLPurifier_URIFilter_MakeAbsoluteTest extends HTMLPurifier_URIFilterHarness +{ + + function setUp() { + parent::setUp(); + $this->filter = new HTMLPurifier_URIFilter_MakeAbsolute(); + $this->setBase(); + } + + function setBase($base = 'http://example.com/foo/bar.html?q=s#frag') { + $this->config->set('URI.Base', $base); + } + + // corresponding to RFC 2396 + + function testPreserveAbsolute() { + $this->assertFiltering('http://example.com/foo.html'); + } + + function testFilterBlank() { + $this->assertFiltering('', 'http://example.com/foo/bar.html?q=s'); + } + + function testFilterEmptyPath() { + $this->assertFiltering('?q=s#frag', 'http://example.com/foo/bar.html?q=s#frag'); + } + + function testPreserveAltScheme() { + $this->assertFiltering('mailto:bob@example.com'); + } + + function testFilterIgnoreHTTPSpecialCase() { + $this->assertFiltering('http:/', 'http://example.com/'); + } + + function testFilterAbsolutePath() { + $this->assertFiltering('/foo.txt', 'http://example.com/foo.txt'); + } + + function testFilterRelativePath() { + $this->assertFiltering('baz.txt', 'http://example.com/foo/baz.txt'); + } + + function testFilterRelativePathWithInternalDot() { + $this->assertFiltering('./baz.txt', 'http://example.com/foo/baz.txt'); + } + + function testFilterRelativePathWithEndingDot() { + $this->assertFiltering('baz/.', 'http://example.com/foo/baz/'); + } + + function testFilterRelativePathDot() { + $this->assertFiltering('.', 'http://example.com/foo/'); + } + + function testFilterRelativePathMultiDot() { + $this->assertFiltering('././foo/./bar/.././baz', 'http://example.com/foo/foo/baz'); + } + + function testFilterAbsolutePathWithDot() { + $this->assertFiltering('/./foo', 'http://example.com/foo'); + } + + function testFilterAbsolutePathWithMultiDot() { + $this->assertFiltering('/./foo/../bar/.', 'http://example.com/bar/'); + } + + function testFilterRelativePathWithInternalDotDot() { + $this->assertFiltering('../baz.txt', 'http://example.com/baz.txt'); + } + + function testFilterRelativePathWithEndingDotDot() { + $this->assertFiltering('..', 'http://example.com/'); + } + + function testFilterRelativePathTooManyDotDots() { + $this->assertFiltering('../../', 'http://example.com/'); + } + + function testFilterAppendingQueryAndFragment() { + $this->assertFiltering('/foo.php?q=s#frag', 'http://example.com/foo.php?q=s#frag'); + } + + // edge cases below + + function testFilterAbsolutePathBase() { + $this->setBase('/foo/baz.txt'); + $this->assertFiltering('test.php', '/foo/test.php'); + } + + function testFilterAbsolutePathBaseDirectory() { + $this->setBase('/foo/'); + $this->assertFiltering('test.php', '/foo/test.php'); + } + + function testFilterAbsolutePathBaseBelow() { + $this->setBase('/foo/baz.txt'); + $this->assertFiltering('../../test.php', '/test.php'); + } + + function testFilterRelativePathBase() { + $this->setBase('foo/baz.html'); + $this->assertFiltering('foo.php', 'foo/foo.php'); + } + + function testFilterRelativePathBaseBelow() { + $this->setBase('../baz.html'); + $this->assertFiltering('test/strike.html', '../test/strike.html'); + } + + function testFilterRelativePathBaseWithAbsoluteURI() { + $this->setBase('../baz.html'); + $this->assertFiltering('/test/strike.html'); + } + + function testFilterRelativePathBaseWithDot() { + $this->setBase('../baz.html'); + $this->assertFiltering('.', '../'); + } + + function testRemoveJavaScriptWithEmbeddedLink() { + // credits: NykO18 + $this->setBase('http://www.example.com/'); + $this->assertFiltering('javascript: window.location = \'http://www.example.com\';', false); + } + + // miscellaneous + + function testFilterDomainWithNoSlash() { + $this->setBase('http://example.com'); + $this->assertFiltering('foo', 'http://example.com/foo'); + } + + // error case + + function testErrorNoBase() { + $this->setBase(null); + $this->expectError('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration'); + $this->assertFiltering('foo/bar.txt'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MungeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MungeTest.php new file mode 100644 index 000000000..1acf11ea7 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilter/MungeTest.php @@ -0,0 +1,145 @@ +<?php + +class HTMLPurifier_URIFilter_MungeTest extends HTMLPurifier_URIFilterHarness +{ + + function setUp() { + parent::setUp(); + $this->filter = new HTMLPurifier_URIFilter_Munge(); + } + + protected function setMunge($uri = 'http://www.google.com/url?q=%s') { + $this->config->set('URI.Munge', $uri); + } + + protected function setSecureMunge($key = 'secret') { + $this->setMunge('/redirect.php?url=%s&checksum=%t'); + $this->config->set('URI.MungeSecretKey', $key); + } + + function testMunge() { + $this->setMunge(); + $this->assertFiltering( + 'http://www.example.com/', + 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F' + ); + } + + function testMungeReplaceTagName() { + $this->setMunge('/r?tagname=%n&url=%s'); + $token = new HTMLPurifier_Token_Start('a'); + $this->context->register('CurrentToken', $token); + $this->assertFiltering('http://google.com', '/r?tagname=a&url=http%3A%2F%2Fgoogle.com'); + } + + function testMungeReplaceAttribute() { + $this->setMunge('/r?attr=%m&url=%s'); + $attr = 'href'; + $this->context->register('CurrentAttr', $attr); + $this->assertFiltering('http://google.com', '/r?attr=href&url=http%3A%2F%2Fgoogle.com'); + } + + function testMungeReplaceResource() { + $this->setMunge('/r?embeds=%r&url=%s'); + $embeds = false; + $this->context->register('EmbeddedURI', $embeds); + $this->assertFiltering('http://google.com', '/r?embeds=&url=http%3A%2F%2Fgoogle.com'); + } + + function testMungeReplaceCSSProperty() { + $this->setMunge('/r?property=%p&url=%s'); + $property = 'background'; + $this->context->register('CurrentCSSProperty', $property); + $this->assertFiltering('http://google.com', '/r?property=background&url=http%3A%2F%2Fgoogle.com'); + } + + function testIgnoreEmbedded() { + $this->setMunge(); + $embeds = true; + $this->context->register('EmbeddedURI', $embeds); + $this->assertFiltering('http://example.com'); + } + + function testProcessEmbedded() { + $this->setMunge(); + $this->config->set('URI.MungeResources', true); + $embeds = true; + $this->context->register('EmbeddedURI', $embeds); + $this->assertFiltering('http://www.example.com/', 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F'); + } + + function testPreserveRelative() { + $this->setMunge(); + $this->assertFiltering('index.html'); + } + + function testMungeIgnoreUnknownSchemes() { + $this->setMunge(); + $this->assertFiltering('javascript:foobar();', true); + } + + function testSecureMungePreserve() { + $this->setSecureMunge(); + $this->assertFiltering('/local'); + } + + function testSecureMungePreserveEmbedded() { + $this->setSecureMunge(); + $embedded = true; + $this->context->register('EmbeddedURI', $embedded); + $this->assertFiltering('http://google.com'); + } + + function testSecureMungeStandard() { + $this->setSecureMunge(); + $this->assertFiltering('http://google.com', '/redirect.php?url=http%3A%2F%2Fgoogle.com&checksum=0072e2f817fd2844825def74e54443debecf0892'); + } + + function testSecureMungeIgnoreUnknownSchemes() { + // This should be integration tested as well to be false + $this->setSecureMunge(); + $this->assertFiltering('javascript:', true); + } + + function testSecureMungeIgnoreUnbrowsableSchemes() { + $this->setSecureMunge(); + $this->assertFiltering('news:', true); + } + + function testSecureMungeToDirectory() { + $this->setSecureMunge(); + $this->setMunge('/links/%s/%t'); + $this->assertFiltering('http://google.com', '/links/http%3A%2F%2Fgoogle.com/0072e2f817fd2844825def74e54443debecf0892'); + } + + function testMungeIgnoreSameDomain() { + $this->setMunge('http://example.com/%s'); + $this->assertFiltering('http://example.com/foobar'); + } + + function testMungeIgnoreSameDomainInsecureToSecure() { + $this->setMunge('http://example.com/%s'); + $this->assertFiltering('https://example.com/foobar'); + } + + function testMungeIgnoreSameDomainSecureToSecure() { + $this->config->set('URI.Base', 'https://example.com'); + $this->setMunge('http://example.com/%s'); + $this->assertFiltering('https://example.com/foobar'); + } + + function testMungeSameDomainSecureToInsecure() { + $this->config->set('URI.Base', 'https://example.com'); + $this->setMunge('/%s'); + $this->assertFiltering('http://example.com/foobar', '/http%3A%2F%2Fexample.com%2Ffoobar'); + } + + function testMungeIgnoresSourceHost() { + $this->config->set('URI.Host', 'foo.example.com'); + $this->setMunge('http://example.com/%s'); + $this->assertFiltering('http://foo.example.com/bar'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIFilterHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/URIFilterHarness.php new file mode 100644 index 000000000..165ae6788 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIFilterHarness.php @@ -0,0 +1,15 @@ +<?php + +class HTMLPurifier_URIFilterHarness extends HTMLPurifier_URIHarness +{ + + protected function assertFiltering($uri, $expect_uri = true) { + $this->prepareURI($uri, $expect_uri); + $this->filter->prepare($this->config, $this->context); + $result = $this->filter->filter($uri, $this->config, $this->context); + $this->assertEitherFailOrIdentical($result, $uri, $expect_uri); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/URIHarness.php new file mode 100644 index 000000000..852460a85 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIHarness.php @@ -0,0 +1,31 @@ +<?php + +class HTMLPurifier_URIHarness extends HTMLPurifier_Harness +{ + + /** + * Prepares two URIs into object form + * @param &$uri Reference to string input URI + * @param &$expect_uri Reference to string expectation URI + * @note If $expect_uri is false, it will stay false + */ + protected function prepareURI(&$uri, &$expect_uri) { + $parser = new HTMLPurifier_URIParser(); + if ($expect_uri === true) $expect_uri = $uri; + $uri = $parser->parse($uri); + if ($expect_uri !== false) { + $expect_uri = $parser->parse($expect_uri); + } + } + + /** + * Generates a URI object from the corresponding string + */ + protected function createURI($uri) { + $parser = new HTMLPurifier_URIParser(); + return $parser->parse($uri); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URIParserTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URIParserTest.php new file mode 100644 index 000000000..5cb5850f7 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URIParserTest.php @@ -0,0 +1,145 @@ +<?php + +class HTMLPurifier_URIParserTest extends HTMLPurifier_Harness +{ + + protected function assertParsing( + $uri, $scheme, $userinfo, $host, $port, $path, $query, $fragment, $config = null, $context = null + ) { + $this->prepareCommon($config, $context); + $parser = new HTMLPurifier_URIParser(); + $result = $parser->parse($uri, $config, $context); + $expect = new HTMLPurifier_URI($scheme, $userinfo, $host, $port, $path, $query, $fragment); + $this->assertEqual($result, $expect); + } + + function testPercentNormalization() { + $this->assertParsing( + '%G', + null, null, null, null, '%25G', null, null + ); + } + + function testRegular() { + $this->assertParsing( + 'http://www.example.com/webhp?q=foo#result2', + 'http', null, 'www.example.com', null, '/webhp', 'q=foo', 'result2' + ); + } + + function testPortAndUsername() { + $this->assertParsing( + 'http://user@authority.part:80/now/the/path?query#fragment', + 'http', 'user', 'authority.part', 80, '/now/the/path', 'query', 'fragment' + ); + } + + function testPercentEncoding() { + $this->assertParsing( + 'http://en.wikipedia.org/wiki/Clich%C3%A9', + 'http', null, 'en.wikipedia.org', null, '/wiki/Clich%C3%A9', null, null + ); + } + + function testEmptyQuery() { + $this->assertParsing( + 'http://www.example.com/?#', + 'http', null, 'www.example.com', null, '/', '', null + ); + } + + function testEmptyPath() { + $this->assertParsing( + 'http://www.example.com', + 'http', null, 'www.example.com', null, '', null, null + ); + } + + function testOpaqueURI() { + $this->assertParsing( + 'mailto:bob@example.com', + 'mailto', null, null, null, 'bob@example.com', null, null + ); + } + + function testIPv4Address() { + $this->assertParsing( + 'http://192.0.34.166/', + 'http', null, '192.0.34.166', null, '/', null, null + ); + } + + function testFakeIPv4Address() { + $this->assertParsing( + 'http://333.123.32.123/', + 'http', null, '333.123.32.123', null, '/', null, null + ); + } + + function testIPv6Address() { + $this->assertParsing( + 'http://[2001:db8::7]/c=GB?objectClass?one', + 'http', null, '[2001:db8::7]', null, '/c=GB', 'objectClass?one', null + ); + } + + function testInternationalizedDomainName() { + $this->assertParsing( + "http://t\xC5\xABdali\xC5\x86.lv", + 'http', null, "t\xC5\xABdali\xC5\x86.lv", null, '', null, null + ); + } + + function testInvalidPort() { + $this->assertParsing( + 'http://example.com:foobar', + 'http', null, 'example.com', null, '', null, null + ); + } + + function testPathAbsolute() { + $this->assertParsing( + 'http:/this/is/path', + 'http', null, null, null, '/this/is/path', null, null + ); + } + + function testPathRootless() { + // this should not be used but is allowed + $this->assertParsing( + 'http:this/is/path', + 'http', null, null, null, 'this/is/path', null, null + ); + } + + function testPathEmpty() { + $this->assertParsing( + 'http:', + 'http', null, null, null, '', null, null + ); + } + + function testRelativeURI() { + $this->assertParsing( + '/a/b', + null, null, null, null, '/a/b', null, null + ); + } + + function testMalformedTag() { + $this->assertParsing( + 'http://www.example.com/>', + 'http', null, 'www.example.com', null, '/', null, null + ); + } + + function testEmpty() { + $this->assertParsing( + '', + null, null, null, null, '', null, null + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URISchemeRegistryTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URISchemeRegistryTest.php new file mode 100644 index 000000000..e124aa18d --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URISchemeRegistryTest.php @@ -0,0 +1,49 @@ +<?php + +class HTMLPurifier_URISchemeRegistryTest extends HTMLPurifier_Harness +{ + + function test() { + + generate_mock_once('HTMLPurifier_URIScheme'); + + $config = HTMLPurifier_Config::create(array( + 'URI.AllowedSchemes' => 'http, telnet', + 'URI.OverrideAllowedSchemes' => true + )); + $context = new HTMLPurifier_Context(); + + $registry = new HTMLPurifier_URISchemeRegistry(); + $this->assertIsA($registry->getScheme('http', $config, $context), 'HTMLPurifier_URIScheme_http'); + + $scheme_http = new HTMLPurifier_URISchemeMock(); + $scheme_telnet = new HTMLPurifier_URISchemeMock(); + $scheme_foobar = new HTMLPurifier_URISchemeMock(); + + // register a new scheme + $registry->register('telnet', $scheme_telnet); + $this->assertIdentical($registry->getScheme('telnet', $config, $context), $scheme_telnet); + + // overload a scheme, this is FINAL (forget about defaults) + $registry->register('http', $scheme_http); + $this->assertIdentical($registry->getScheme('http', $config, $context), $scheme_http); + + // when we register a scheme, it's automatically allowed + $registry->register('foobar', $scheme_foobar); + $this->assertIdentical($registry->getScheme('foobar', $config, $context), $scheme_foobar); + + // now, test when overriding is not allowed + $config = HTMLPurifier_Config::create(array( + 'URI.AllowedSchemes' => 'http, telnet', + 'URI.OverrideAllowedSchemes' => false + )); + $this->assertNull($registry->getScheme('foobar', $config, $context)); + + // scheme not allowed and never registered + $this->assertNull($registry->getScheme('ftp', $config, $context)); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URISchemeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/URISchemeTest.php new file mode 100644 index 000000000..4b43310bb --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URISchemeTest.php @@ -0,0 +1,188 @@ +<?php + +// WARNING: All the URI schemes are far to relaxed, we need to tighten +// the checks. + +class HTMLPurifier_URISchemeTest extends HTMLPurifier_URIHarness +{ + + private $pngBase64; + + public function __construct() { + $this->pngBase64 = + 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP'. + 'C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA'. + 'AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J'. + 'REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq'. + 'ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0'. + 'vr4MkhoXe0rZigAAAABJRU5ErkJggg=='; + } + + protected function assertValidation($uri, $expect_uri = true) { + $this->prepareURI($uri, $expect_uri); + $this->config->set('URI.AllowedSchemes', array($uri->scheme)); + // convenience hack: the scheme should be explicitly specified + $scheme = $uri->getSchemeObj($this->config, $this->context); + $result = $scheme->validate($uri, $this->config, $this->context); + $this->assertEitherFailOrIdentical($result, $uri, $expect_uri); + } + + function test_http_regular() { + $this->assertValidation( + 'http://example.com/?s=q#fragment' + ); + } + + function test_http_removeDefaultPort() { + $this->assertValidation( + 'http://example.com:80', + 'http://example.com' + ); + } + + function test_http_removeUserInfo() { + $this->assertValidation( + 'http://bob@example.com', + 'http://example.com' + ); + } + + function test_http_preserveNonDefaultPort() { + $this->assertValidation( + 'http://example.com:8080' + ); + } + + function test_https_regular() { + $this->assertValidation( + 'https://user@example.com:443/?s=q#frag', + 'https://example.com/?s=q#frag' + ); + } + + function test_ftp_regular() { + $this->assertValidation( + 'ftp://user@example.com/path' + ); + } + + function test_ftp_removeDefaultPort() { + $this->assertValidation( + 'ftp://example.com:21', + 'ftp://example.com' + ); + } + + function test_ftp_removeQueryString() { + $this->assertValidation( + 'ftp://example.com?s=q', + 'ftp://example.com' + ); + } + + function test_ftp_preserveValidTypecode() { + $this->assertValidation( + 'ftp://example.com/file.txt;type=a' + ); + } + + function test_ftp_removeInvalidTypecode() { + $this->assertValidation( + 'ftp://example.com/file.txt;type=z', + 'ftp://example.com/file.txt' + ); + } + + function test_ftp_encodeExtraSemicolons() { + $this->assertValidation( + 'ftp://example.com/too;many;semicolons=1', + 'ftp://example.com/too%3Bmany%3Bsemicolons=1' + ); + } + + function test_news_regular() { + $this->assertValidation( + 'news:gmane.science.linguistics' + ); + } + + function test_news_explicit() { + $this->assertValidation( + 'news:642@eagle.ATT.COM' + ); + } + + function test_news_removeNonPathComponents() { + $this->assertValidation( + 'news://user@example.com:80/rec.music?path=foo#frag', + 'news:/rec.music#frag' + ); + } + + function test_nntp_regular() { + $this->assertValidation( + 'nntp://news.example.com/alt.misc/42#frag' + ); + } + + function test_nntp_removalOfRedundantOrUselessComponents() { + $this->assertValidation( + 'nntp://user@news.example.com:119/alt.misc/42?s=q#frag', + 'nntp://news.example.com/alt.misc/42#frag' + ); + } + + function test_mailto_regular() { + $this->assertValidation( + 'mailto:bob@example.com' + ); + } + + function test_mailto_removalOfRedundantOrUselessComponents() { + $this->assertValidation( + 'mailto://user@example.com:80/bob@example.com?subject=Foo#frag', + 'mailto:/bob@example.com?subject=Foo#frag' + ); + } + + function test_data_png() { + $this->assertValidation( + 'data:image/png;base64,'.$this->pngBase64 + ); + } + + function test_data_malformed() { + $this->assertValidation( + 'data:image/png;base64,vr4MkhoXJRU5ErkJggg==', + false + ); + } + + function test_data_implicit() { + $this->assertValidation( + 'data:base64,'.$this->pngBase64, + 'data:image/png;base64,'.$this->pngBase64 + ); + } + + function test_file_basic() { + $this->assertValidation( + 'file://user@MYCOMPUTER:12/foo/bar?baz#frag', + 'file://MYCOMPUTER/foo/bar#frag' + ); + } + + function test_file_local() { + $this->assertValidation( + 'file:///foo/bar?baz#frag', + 'file:///foo/bar#frag' + ); + } + + function test_ftp_empty_host() { + $this->assertValidation('ftp:///example.com', false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/URITest.php b/lib/htmlpurifier/tests/HTMLPurifier/URITest.php new file mode 100644 index 000000000..02b950133 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/URITest.php @@ -0,0 +1,201 @@ +<?php + +class HTMLPurifier_URITest extends HTMLPurifier_URIHarness +{ + + protected function createURI($uri) { + $parser = new HTMLPurifier_URIParser(); + return $parser->parse($uri); + } + + function test_construct() { + $uri1 = new HTMLPurifier_URI('HTTP', 'bob', 'example.com', '23', '/foo', 'bar=2', 'slash'); + $uri2 = new HTMLPurifier_URI('http', 'bob', 'example.com', 23, '/foo', 'bar=2', 'slash'); + $this->assertIdentical($uri1, $uri2); + } + + protected $oldRegistry; + + protected function &setUpSchemeRegistryMock() { + $this->oldRegistry = HTMLPurifier_URISchemeRegistry::instance(); + generate_mock_once('HTMLPurifier_URIScheme'); + generate_mock_once('HTMLPurifier_URISchemeRegistry'); + $registry = HTMLPurifier_URISchemeRegistry::instance( + new HTMLPurifier_URISchemeRegistryMock() + ); + return $registry; + } + + protected function setUpSchemeMock($name) { + $registry = $this->setUpSchemeRegistryMock(); + $scheme_mock = new HTMLPurifier_URISchemeMock(); + $registry->setReturnValue('getScheme', $scheme_mock, array($name, '*', '*')); + return $scheme_mock; + } + + protected function setUpNoValidSchemes() { + $registry = $this->setUpSchemeRegistryMock(); + $registry->setReturnValue('getScheme', false, array('*', '*', '*')); + } + + protected function tearDownSchemeRegistryMock() { + HTMLPurifier_URISchemeRegistry::instance($this->oldRegistry); + } + + function test_getSchemeObj() { + $scheme_mock = $this->setUpSchemeMock('http'); + + $uri = $this->createURI('http:'); + $scheme_obj = $uri->getSchemeObj($this->config, $this->context); + $this->assertIdentical($scheme_obj, $scheme_mock); + + $this->tearDownSchemeRegistryMock(); + } + + function test_getSchemeObj_invalidScheme() { + $this->setUpNoValidSchemes(); + + $uri = $this->createURI('http:'); + $result = $uri->getSchemeObj($this->config, $this->context); + $this->assertIdentical($result, false); + + $this->tearDownSchemeRegistryMock(); + } + + function test_getSchemaObj_defaultScheme() { + $scheme = 'foobar'; + + $scheme_mock = $this->setUpSchemeMock($scheme); + $this->config->set('URI.DefaultScheme', $scheme); + + $uri = $this->createURI('hmm'); + $scheme_obj = $uri->getSchemeObj($this->config, $this->context); + $this->assertIdentical($scheme_obj, $scheme_mock); + + $this->tearDownSchemeRegistryMock(); + } + + function test_getSchemaObj_invalidDefaultScheme() { + $this->setUpNoValidSchemes(); + $this->config->set('URI.DefaultScheme', 'foobar'); + + $uri = $this->createURI('hmm'); + + $this->expectError('Default scheme object "foobar" was not readable'); + $result = $uri->getSchemeObj($this->config, $this->context); + $this->assertIdentical($result, false); + + $this->tearDownSchemeRegistryMock(); + } + + protected function assertToString($expect_uri, $scheme, $userinfo, $host, $port, $path, $query, $fragment) { + $uri = new HTMLPurifier_URI($scheme, $userinfo, $host, $port, $path, $query, $fragment); + $string = $uri->toString(); + $this->assertIdentical($string, $expect_uri); + } + + function test_toString_full() { + $this->assertToString( + 'http://bob@example.com:300/foo?bar=baz#fragment', + 'http', 'bob', 'example.com', 300, '/foo', 'bar=baz', 'fragment' + ); + } + + function test_toString_scheme() { + $this->assertToString( + 'http:', + 'http', null, null, null, '', null, null + ); + } + + function test_toString_authority() { + $this->assertToString( + '//bob@example.com:8080', + null, 'bob', 'example.com', 8080, '', null, null + ); + } + + function test_toString_path() { + $this->assertToString( + '/path/to', + null, null, null, null, '/path/to', null, null + ); + } + + function test_toString_query() { + $this->assertToString( + '?q=string', + null, null, null, null, '', 'q=string', null + ); + } + + function test_toString_fragment() { + $this->assertToString( + '#fragment', + null, null, null, null, '', null, 'fragment' + ); + } + + protected function assertValidation($uri, $expect_uri = true) { + if ($expect_uri === true) $expect_uri = $uri; + $uri = $this->createURI($uri); + $result = $uri->validate($this->config, $this->context); + if ($expect_uri === false) { + $this->assertFalse($result); + } else { + $this->assertTrue($result); + $this->assertIdentical($uri->toString(), $expect_uri); + } + } + + function test_validate_overlongPort() { + $this->assertValidation('http://example.com:65536', 'http://example.com'); + } + + function test_validate_zeroPort() { + $this->assertValidation('http://example.com:00', 'http://example.com'); + } + + function test_validate_invalidHostThatLooksLikeIPv6() { + $this->assertValidation('http://[2001:0db8:85z3:08d3:1319:8a2e:0370:7334]', ''); + } + + function test_validate_removeRedundantScheme() { + $this->assertValidation('http:foo:/:', 'foo%3A/:'); + } + + function test_validate_username() { + $this->assertValidation("http://user\xE3\x91\x94:@foo.com", 'http://user%E3%91%94:@foo.com'); + } + + function test_validate_path_abempty() { + $this->assertValidation("http://host/\xE3\x91\x94:", 'http://host/%E3%91%94:'); + } + + function test_validate_path_absolute() { + $this->assertValidation("/\xE3\x91\x94:", '/%E3%91%94:'); + } + + function test_validate_path_rootless() { + $this->assertValidation("mailto:\xE3\x91\x94:", 'mailto:%E3%91%94:'); + } + + function test_validate_path_noscheme() { + $this->assertValidation("\xE3\x91\x94", '%E3%91%94'); + } + + function test_validate_query() { + $this->assertValidation("?/\xE3\x91\x94", '?/%E3%91%94'); + } + + function test_validate_fragment() { + $this->assertValidation("#/\xE3\x91\x94", '#/%E3%91%94'); + } + + function test_validate_path_empty() { + $this->assertValidation('http://google.com'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/UnitConverterTest.php b/lib/htmlpurifier/tests/HTMLPurifier/UnitConverterTest.php new file mode 100644 index 000000000..3dc1e2afa --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/UnitConverterTest.php @@ -0,0 +1,131 @@ +<?php + +class HTMLPurifier_UnitConverterTest extends HTMLPurifier_Harness +{ + + protected function assertConversion($input, $expect, $unit = null, $test_negative = true) { + $length = HTMLPurifier_Length::make($input); + if ($expect !== false) $expectl = HTMLPurifier_Length::make($expect); + else $expectl = false; + $to_unit = $unit !== null ? $unit : $expectl->getUnit(); + + $converter = new HTMLPurifier_UnitConverter(4, 10); + $result = $converter->convert($length, $to_unit); + if (!$result || !$expectl) $this->assertIdentical($result, $expectl); + else $this->assertIdentical($result->toString(), $expectl->toString()); + + $converter = new HTMLPurifier_UnitConverter(4, 10, true); + $result = $converter->convert($length, $to_unit); + if (!$result || !$expectl) $this->assertIdentical($result, $expectl); + else $this->assertIdentical($result->toString(), $expectl->toString(), 'BCMath substitute: %s'); + + if ($test_negative) { + $this->assertConversion( + "-$input", + $expect === false ? false : "-$expect", + $unit, + false + ); + } + } + + function testFail() { + $this->assertConversion('1in', false, 'foo'); + $this->assertConversion('1foo', false, 'in'); + } + + function testZero() { + $this->assertConversion('0', '0', 'in', false); + $this->assertConversion('-0', '0', 'in', false); + $this->assertConversion('0in', '0', 'in', false); + $this->assertConversion('-0in', '0', 'in', false); + $this->assertConversion('0in', '0', 'pt', false); + $this->assertConversion('-0in', '0', 'pt', false); + } + + function testEnglish() { + $this->assertConversion('1in', '6pc'); + $this->assertConversion('6pc', '1in'); + + $this->assertConversion('1in', '72pt'); + $this->assertConversion('72pt', '1in'); + + $this->assertConversion('1pc', '12pt'); + $this->assertConversion('12pt', '1pc'); + + $this->assertConversion('1pt', '0.01389in'); + $this->assertConversion('1.000pt', '0.01389in'); + $this->assertConversion('100000pt', '1389in'); + + $this->assertConversion('1in', '96px'); + $this->assertConversion('96px', '1in'); + } + + function testMetric() { + $this->assertConversion('1cm', '10mm'); + $this->assertConversion('10mm', '1cm'); + $this->assertConversion('1mm', '0.1cm'); + $this->assertConversion('100mm', '10cm'); + } + + function testEnglishMetric() { + $this->assertConversion('2.835pt', '1mm'); + $this->assertConversion('1mm', '2.835pt'); + $this->assertConversion('0.3937in', '1cm'); + } + + function testRoundingMinPrecision() { + // One sig-fig, modified to be four, conversion rounds up + $this->assertConversion('100pt', '1.389in'); + $this->assertConversion('1000pt', '13.89in'); + $this->assertConversion('10000pt', '138.9in'); + $this->assertConversion('100000pt', '1389in'); + $this->assertConversion('1000000pt', '13890in'); + } + + function testRoundingUserPrecision() { + // Five sig-figs, conversion rounds down + $this->assertConversion('11112000pt', '154330in'); + $this->assertConversion('1111200pt', '15433in'); + $this->assertConversion('111120pt', '1543.3in'); + $this->assertConversion('11112pt', '154.33in'); + $this->assertConversion('1111.2pt', '15.433in'); + $this->assertConversion('111.12pt', '1.5433in'); + $this->assertConversion('11.112pt', '0.15433in'); + } + + function testRoundingBigNumber() { + $this->assertConversion('444400000000000000000000in', '42660000000000000000000000px'); + } + + protected function assertSigFig($n, $sigfigs) { + $converter = new HTMLPurifier_UnitConverter(); + $result = $converter->getSigFigs($n); + $this->assertIdentical($result, $sigfigs); + } + + function test_getSigFigs() { + $this->assertSigFig('0', 0); + $this->assertSigFig('1', 1); + $this->assertSigFig('-1', 1); + $this->assertSigFig('+1', 1); + $this->assertSigFig('01', 1); + $this->assertSigFig('001', 1); + $this->assertSigFig('12', 2); + $this->assertSigFig('012', 2); + $this->assertSigFig('10', 1); + $this->assertSigFig('10.', 2); + $this->assertSigFig('100.', 3); + $this->assertSigFig('103', 3); + $this->assertSigFig('130', 2); + $this->assertSigFig('.1', 1); + $this->assertSigFig('0.1', 1); + $this->assertSigFig('00.1', 1); + $this->assertSigFig('0.01', 1); + $this->assertSigFig('0.010', 2); + $this->assertSigFig('0.012', 2); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/VarParser/FlexibleTest.php b/lib/htmlpurifier/tests/HTMLPurifier/VarParser/FlexibleTest.php new file mode 100644 index 000000000..c6149b6b4 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/VarParser/FlexibleTest.php @@ -0,0 +1,66 @@ +<?php + +class HTMLPurifier_VarParser_FlexibleTest extends HTMLPurifier_VarParserHarness +{ + + function testValidate() { + + $this->assertValid('foobar', 'string'); + $this->assertValid('foobar', 'text'); + $this->assertValid('FOOBAR', 'istring', 'foobar'); + $this->assertValid('FOOBAR', 'itext', 'foobar'); + + $this->assertValid(34, 'int'); + + $this->assertValid(3.34, 'float'); + + $this->assertValid(false, 'bool'); + $this->assertValid(0, 'bool', false); + $this->assertValid(1, 'bool', true); + $this->assertValid('true', 'bool', true); + $this->assertValid('false', 'bool', false); + $this->assertValid('1', 'bool', true); + $this->assertInvalid(34, 'bool'); + $this->assertInvalid(null, 'bool'); + + $this->assertValid(array('1', '2', '3'), 'list'); + $this->assertValid('foo,bar, cow', 'list', array('foo', 'bar', 'cow')); + $this->assertValid('', 'list', array()); + $this->assertValid("foo\nbar", 'list', array('foo', 'bar')); + $this->assertValid("foo\nbar,baz", 'list', array('foo', 'bar', 'baz')); + + $this->assertValid(array('1' => true, '2' => true), 'lookup'); + $this->assertValid(array('1', '2'), 'lookup', array('1' => true, '2' => true)); + $this->assertValid('foo,bar', 'lookup', array('foo' => true, 'bar' => true)); + $this->assertValid("foo\nbar", 'lookup', array('foo' => true, 'bar' => true)); + $this->assertValid("foo\nbar,baz", 'lookup', array('foo' => true, 'bar' => true, 'baz' => true)); + $this->assertValid('', 'lookup', array()); + $this->assertValid(array(), 'lookup'); + + $this->assertValid(array('foo' => 'bar'), 'hash'); + $this->assertValid(array(1 => 'moo'), 'hash'); + $this->assertInvalid(array(0 => 'moo'), 'hash'); + $this->assertValid('', 'hash', array()); + $this->assertValid('foo:bar,too:two', 'hash', array('foo' => 'bar', 'too' => 'two')); + $this->assertValid("foo:bar\ntoo:two,three:free", 'hash', array('foo' => 'bar', 'too' => 'two', 'three' => 'free')); + $this->assertValid('foo:bar,too', 'hash', array('foo' => 'bar')); + $this->assertValid('foo:bar,', 'hash', array('foo' => 'bar')); + $this->assertValid('foo:bar:baz', 'hash', array('foo' => 'bar:baz')); + + $this->assertValid(23, 'mixed'); + + } + + function testValidate_withMagicNumbers() { + $this->assertValid('foobar', HTMLPurifier_VarParser::STRING); + } + + function testValidate_null() { + $this->assertIdentical($this->parser->parse(null, 'string', true), null); + $this->expectException('HTMLPurifier_VarParserException'); + $this->parser->parse(null, 'string', false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/VarParser/NativeTest.php b/lib/htmlpurifier/tests/HTMLPurifier/VarParser/NativeTest.php new file mode 100644 index 000000000..424dceb75 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/VarParser/NativeTest.php @@ -0,0 +1,12 @@ +<?php + +class HTMLPurifier_VarParser_NativeTest extends HTMLPurifier_VarParserHarness +{ + + public function testValidateSimple() { + $this->assertValid('"foo\\\\"', 'string', 'foo\\'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifier/VarParserHarness.php b/lib/htmlpurifier/tests/HTMLPurifier/VarParserHarness.php new file mode 100644 index 000000000..0ef23189c --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifier/VarParserHarness.php @@ -0,0 +1,33 @@ +<?php + +class HTMLPurifier_VarParserHarness extends UnitTestCase +{ + + protected $parser; + + public function setup() { + $class = substr(get_class($this), 0, -4); + $this->parser = new $class(); + } + + function assertValid($var, $type, $ret = null) { + $ret = ($ret === null) ? $var : $ret; + $this->assertIdentical($this->parser->parse($var, $type), $ret); + } + + function assertInvalid($var, $type, $msg = null) { + $caught = false; + try { + $this->parser->parse($var, $type); + } catch (HTMLPurifier_VarParserException $e) { + $caught = true; + if ($msg !== null) $this->assertIdentical($e->getMessage(), $msg); + } + if (!$caught) { + $this->fail('Did not catch expected error'); + } + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/HTMLPurifierTest.php b/lib/htmlpurifier/tests/HTMLPurifierTest.php new file mode 100644 index 000000000..4fe8f0680 --- /dev/null +++ b/lib/htmlpurifier/tests/HTMLPurifierTest.php @@ -0,0 +1,50 @@ +<?php + +class HTMLPurifierTest extends HTMLPurifier_Harness +{ + protected $purifier; + + function testNull() { + $this->assertPurification("Null byte\0", "Null byte"); + } + + function test_purifyArray() { + + $this->assertIdentical( + $this->purifier->purifyArray( + array('Good', '<b>Sketchy', 'foo' => '<script>bad</script>') + ), + array('Good', '<b>Sketchy</b>', 'foo' => '') + ); + + $this->assertIsA($this->purifier->context, 'array'); + + } + + function testGetInstance() { + $purifier = HTMLPurifier::getInstance(); + $purifier2 = HTMLPurifier::getInstance(); + $this->assertReference($purifier, $purifier2); + } + + function testMakeAbsolute() { + $this->config->set('URI.Base', 'http://example.com/bar/baz.php'); + $this->config->set('URI.MakeAbsolute', true); + $this->assertPurification( + '<a href="foo.txt">Foobar</a>', + '<a href="http://example.com/bar/foo.txt">Foobar</a>' + ); + } + + function test_addFilter_deprecated() { + $this->expectError('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom'); + generate_mock_once('HTMLPurifier_Filter'); + $this->purifier->addFilter($mock = new HTMLPurifier_FilterMock()); + $mock->expectOnce('preFilter'); + $mock->expectOnce('postFilter'); + $this->purifier->purify('foo'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/PHPT/Controller/SimpleTest.php b/lib/htmlpurifier/tests/PHPT/Controller/SimpleTest.php new file mode 100644 index 000000000..62fe6a405 --- /dev/null +++ b/lib/htmlpurifier/tests/PHPT/Controller/SimpleTest.php @@ -0,0 +1,24 @@ +<?php + +/** + * Controller for PHPT that implements the SimpleTest unit-testing interface. + */ +class PHPT_Controller_SimpleTest extends SimpleTestCase +{ + + protected $_path; + + public function __construct($path) { + $this->_path = $path; + parent::__construct($path); + } + + public function testPhpt() { + $suite = new PHPT_Suite(array($this->_path)); + $phpt_reporter = new PHPT_Reporter_SimpleTest($this->reporter); + $suite->run($phpt_reporter); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/PHPT/Reporter/SimpleTest.php b/lib/htmlpurifier/tests/PHPT/Reporter/SimpleTest.php new file mode 100644 index 000000000..25ffe7b4c --- /dev/null +++ b/lib/htmlpurifier/tests/PHPT/Reporter/SimpleTest.php @@ -0,0 +1,77 @@ +<?php + +/** + * Proxies results from PHPT_Reporter to SimpleTest's reporter + */ +class PHPT_Reporter_SimpleTest implements PHPT_Reporter +{ + + /** SimpleTest reporter to proxy results to */ + protected $reporter; + + /** @param SimpleTest reporter */ + public function __construct($reporter) { + $this->reporter = $reporter; + } + + // TODO: Figure out what the proper calls should be, since we've given + // each Suite its own UnitTestCase controller + + /** + * Called when the Reporter is started from a PHPT_Suite + * @todo Figure out if Suites can be named + */ + public function onSuiteStart(PHPT_Suite $suite) { + //$this->reporter->paintGroupStart('PHPT Suite', $suite->count()); + } + + /** + * Called when the Reporter is finished in a PHPT_Suite + */ + public function onSuiteEnd(PHPT_Suite $suite) { + //$this->reporter->paintGroupEnd('PHPT Suite'); + } + + /** + * Called when a Case is started + */ + public function onCaseStart(PHPT_Case $case) { + //$this->reporter->paintCaseStart($case->name); + } + + /** + * Called when a Case ends + */ + public function onCaseEnd(PHPT_Case $case) { + //$this->reporter->paintCaseEnd($case->name); + } + + /** + * Called when a Case runs without Exception + */ + public function onCasePass(PHPT_Case $case) { + $this->reporter->paintPass("{$case->name} in {$case->filename}"); + } + + /** + * Called when a PHPT_Case_VetoException is thrown during a Case's run() + */ + public function onCaseSkip(PHPT_Case $case, PHPT_Case_VetoException $veto) { + $this->reporter->paintSkip($veto->getMessage() . ' [' . $case->filename .']'); + } + + /** + * Called when any Exception other than a PHPT_Case_VetoException is encountered + * during a Case's run() + */ + public function onCaseFail(PHPT_Case $case, PHPT_Case_FailureException $failure) { + $this->reporter->paintFail($failure->getReason()); + } + + public function onParserError(Exception $exception) { + $this->reporter->paintException($exception); + } + +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/PHPT/Section/PRESKIPIF.php b/lib/htmlpurifier/tests/PHPT/Section/PRESKIPIF.php new file mode 100644 index 000000000..4691c76da --- /dev/null +++ b/lib/htmlpurifier/tests/PHPT/Section/PRESKIPIF.php @@ -0,0 +1,36 @@ +<?php + +class PHPT_Section_PRESKIPIF implements PHPT_Section_RunnableBefore +{ + private $_data = null; + private $_runner_factory = null; + + public function __construct($data) + { + $this->_data = $data; + $this->_runner_factory = new PHPT_CodeRunner_Factory(); + } + + public function run(PHPT_Case $case) + { + // @todo refactor this code into PHPT_Util class as its used in multiple places + $filename = dirname($case->filename) . '/' . basename($case->filename, '.php') . '.skip.php'; + + // @todo refactor to PHPT_CodeRunner + file_put_contents($filename, $this->_data); + $runner = $this->_runner_factory->factory($case); + $runner->ini = ""; + $response = $runner->run($filename)->output; + unlink($filename); + + if (preg_match('/^skip( - (.*))?/', $response, $matches)) { + $message = !empty($matches[2]) ? $matches[2] : ''; + throw new PHPT_Case_VetoException($message); + } + } + + public function getPriority() + { + return -2; + } +} diff --git a/lib/htmlpurifier/tests/common.php b/lib/htmlpurifier/tests/common.php new file mode 100644 index 000000000..223a5bf1f --- /dev/null +++ b/lib/htmlpurifier/tests/common.php @@ -0,0 +1,231 @@ +<?php + +if (!defined('HTMLPurifierTest')) { + echo "Invalid entry point\n"; + exit; +} + +// setup our own autoload, checking for HTMLPurifier library if spl_autoload_register +// is not allowed +function __autoload($class) { + if (!function_exists('spl_autoload_register')) { + if (HTMLPurifier_Bootstrap::autoload($class)) return true; + if (HTMLPurifierExtras::autoload($class)) return true; + } + require str_replace('_', '/', $class) . '.php'; + return true; +} +if (function_exists('spl_autoload_register')) { + spl_autoload_register('__autoload'); +} + +// default settings (protect against register_globals) +$GLOBALS['HTMLPurifierTest'] = array(); +$GLOBALS['HTMLPurifierTest']['PEAR'] = false; // do PEAR tests +$GLOBALS['HTMLPurifierTest']['PHPT'] = true; // do PHPT tests +$GLOBALS['HTMLPurifierTest']['PH5P'] = class_exists('DOMDocument'); + +// default library settings +$simpletest_location = 'simpletest/'; // reasonable guess +$csstidy_location = false; +$versions_to_test = array(); +$php = 'php'; +$phpv = 'phpv'; + +// load configuration +if (file_exists('../conf/test-settings.php')) include '../conf/test-settings.php'; +elseif (file_exists('../test-settings.php')) include '../test-settings.php'; +else { + throw new Exception('Please create a test-settings.php file by copying test-settings.sample.php and configuring accordingly'); +} + +// load SimpleTest +require_once $simpletest_location . 'unit_tester.php'; +require_once $simpletest_location . 'reporter.php'; +require_once $simpletest_location . 'mock_objects.php'; +require_once $simpletest_location . 'xml.php'; +require_once $simpletest_location . 'remote.php'; + +// load CSS Tidy +if ($csstidy_location !== false) { + $old = error_reporting(E_ALL); + require $csstidy_location . 'class.csstidy.php'; + error_reporting($old); +} + +// load PEAR to include path +if ( is_string($GLOBALS['HTMLPurifierTest']['PEAR']) ) { + // if PEAR is true, there's no need to add it to the path + set_include_path($GLOBALS['HTMLPurifierTest']['PEAR'] . PATH_SEPARATOR . + get_include_path()); +} + +// after external libraries are loaded, turn on compile time errors +error_reporting(E_ALL | E_STRICT); + +// initialize extra HTML Purifier libraries +require '../extras/HTMLPurifierExtras.auto.php'; + +// load SimpleTest addon functions +require 'generate_mock_once.func.php'; +require 'path2class.func.php'; + +/** + * Arguments parser, is cli and web agnostic. + * @warning + * There are some quirks about the argument format: + * - Short boolean flags cannot be chained together + * - Only strings, integers and booleans are accepted + * @param $AC + * Arguments array to populate. This takes a simple format of 'argument' + * => default value. Depending on the type of the default value, + * arguments will be typecast accordingly. For example, if + * 'flag' => false is passed, all arguments for that will be cast to + * boolean. Do *not* pass null, as it will not be recognized. + * @param $aliases + * + */ +function htmlpurifier_parse_args(&$AC, $aliases) { + if (empty($_GET) && !empty($_SERVER['argv'])) { + array_shift($_SERVER['argv']); + $o = false; + $bool = false; + $val_is_bool = false; + foreach ($_SERVER['argv'] as $opt) { + if ($o !== false) { + $v = $opt; + } else { + if ($opt === '') continue; + if (strlen($opt) > 2 && strncmp($opt, '--', 2) === 0) { + $o = substr($opt, 2); + } elseif ($opt[0] == '-') { + $o = substr($opt, 1); + } else { + $lopt = strtolower($opt); + if ($bool !== false && ($opt === '0' || $lopt === 'off' || $lopt === 'no')) { + $o = $bool; + $v = false; + $val_is_bool = true; + } elseif (isset($aliases[''])) { + $o = $aliases['']; + } + } + $bool = false; + if (!isset($AC[$o]) || !is_bool($AC[$o])) { + if (strpos($o, '=') === false) { + continue; + } + list($o, $v) = explode('=', $o); + } elseif (!$val_is_bool) { + $v = true; + $bool = $o; + } + $val_is_bool = false; + } + if ($o === false) continue; + htmlpurifier_args($AC, $aliases, $o, $v); + $o = false; + } + } else { + foreach ($_GET as $o => $v) { + if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) { + $v = stripslashes($v); + } + htmlpurifier_args($AC, $aliases, $o, $v); + } + } +} + +/** + * Actually performs assignment to $AC, see htmlpurifier_parse_args() + * @param $AC Arguments array to write to + * @param $aliases Aliases for options + * @param $o Argument name + * @param $v Argument value + */ +function htmlpurifier_args(&$AC, $aliases, $o, $v) { + if (isset($aliases[$o])) $o = $aliases[$o]; + if (!isset($AC[$o])) return; + if (is_string($AC[$o])) $AC[$o] = $v; + if (is_bool($AC[$o])) $AC[$o] = ($v === '') ? true :(bool) $v; + if (is_int($AC[$o])) $AC[$o] = (int) $v; +} + +/** + * Adds a test-class; we use file extension to determine which class to use. + */ +function htmlpurifier_add_test($test, $test_file, $only_phpt = false) { + switch (strrchr($test_file, ".")) { + case '.phpt': + return $test->add(new PHPT_Controller_SimpleTest($test_file)); + case '.php': + require_once $test_file; + return $test->add(path2class($test_file)); + case '.vtest': + return $test->add(new HTMLPurifier_ConfigSchema_ValidatorTestCase($test_file)); + case '.htmlt': + return $test->add(new HTMLPurifier_HTMLT($test_file)); + default: + trigger_error("$test_file is an invalid file for testing", E_USER_ERROR); + } +} + +/** + * Debugging function that prints tokens in a user-friendly manner. + */ +function printTokens($tokens, $index = null) { + $string = '<pre>'; + $generator = new HTMLPurifier_Generator(HTMLPurifier_Config::createDefault(), new HTMLPurifier_Context); + foreach ($tokens as $i => $token) { + if ($index === $i) $string .= '[<strong>'; + $string .= "<sup>$i</sup>"; + $string .= $generator->escape($generator->generateFromToken($token)); + if ($index === $i) $string .= '</strong>]'; + } + $string .= '</pre>'; + echo $string; +} + +/** + * Convenient "insta-fail" test-case to add if any outside things fail + */ +class FailedTest extends UnitTestCase { + protected $msg, $details; + public function __construct($msg, $details = null) { + $this->msg = $msg; + $this->details = $details; + } + public function test() { + $this->fail($this->msg); + if ($this->details) $this->reporter->paintFormattedMessage($this->details); + } +} + +/** + * Flushes all caches, and fatally errors out if there's a problem. + */ +function htmlpurifier_flush($php, $reporter) { + exec($php . ' ../maintenance/flush.php ' . $php . ' 2>&1', $out, $status); + if ($status) { + $test = new FailedTest( + 'maintenance/flush.php returned non-zero exit status', + wordwrap(implode("\n", $out), 80) + ); + $test->run($reporter); + exit(1); + } +} + +/** + * Dumps error queue, useful if there has been a fatal error. + */ +function htmlpurifier_dump_error_queue() { + $context = SimpleTest::getContext(); + $queue = $context->get('SimpleErrorQueue'); + while (($error = $queue->extract()) !== false) { + var_dump($error); + } +} +register_shutdown_function('htmlpurifier_dump_error_queue'); + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/default_load.php b/lib/htmlpurifier/tests/default_load.php new file mode 100644 index 000000000..9b8bfb70c --- /dev/null +++ b/lib/htmlpurifier/tests/default_load.php @@ -0,0 +1,3 @@ +<?php +class default_load { } +echo "Default loaded\n"; diff --git a/lib/htmlpurifier/tests/generate_mock_once.func.php b/lib/htmlpurifier/tests/generate_mock_once.func.php new file mode 100644 index 000000000..3d8710182 --- /dev/null +++ b/lib/htmlpurifier/tests/generate_mock_once.func.php @@ -0,0 +1,11 @@ +<?php + +// since Mocks can't be called from within test files, we need to do +// a little jumping through hoops to generate them +function generate_mock_once($name) { + $mock_name = $name . 'Mock'; + if (class_exists($mock_name, false)) return false; + Mock::generate($name, $mock_name); +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/index.php b/lib/htmlpurifier/tests/index.php new file mode 100644 index 000000000..808b2ce18 --- /dev/null +++ b/lib/htmlpurifier/tests/index.php @@ -0,0 +1,217 @@ +<?php + +/** @file + * Unit tester + * + * The heart and soul of HTML Purifier's correctness; anything and everything + * is tested here! Arguments are specified like --arg=opt, allowed arguments + * are: + * - flush, whether or not to flush definition caches before running + * - standalone, whether or not to test the standalone version + * - file (f), a single file to test + * - xml, whether or not to output XML + * - dry, whether or not to do a dry run + * - type, the type of tests to run, can be 'htmlpurifier', 'configdoc', + * 'fstools', 'htmlt', 'vtest' or 'phpt' + * + * If you're interested in running the test-cases, mosey over to + * ../test-settings.sample.php, copy the file to test-settings.php and follow + * the enclosed instructions. + * + * @warning File setup does not exactly match with autoloader; make sure that + * non-test classes (i.e. classes that are not retrieved using + * $test_files) do not have underscores in their names. + */ + +// HTML Purifier runs error free on E_STRICT, so if code reports +// errors, we want to know about it. +error_reporting(E_ALL | E_STRICT); + +// Because we always want to know about errors, and because SimpleTest +// will notify us about them, logging the errors to stderr is +// counterproductive and in fact the wrong thing when a test case +// exercises an error condition to detect for it. +ini_set('log_errors', false); + +define('HTMLPurifierTest', 1); +define('HTMLPURIFIER_SCHEMA_STRICT', true); // validate schemas +chdir(dirname(__FILE__)); + +$php = 'php'; // for safety +ini_set('memory_limit', '64M'); + +require 'common.php'; +$AC = array(); // parameters +$AC['flush'] = false; +$AC['standalone'] = false; +$AC['file'] = ''; +$AC['xml'] = false; +$AC['dry'] = false; +$AC['php'] = $php; +$AC['help'] = false; +$AC['verbose'] = false; +$AC['txt'] = false; + +$AC['type'] = ''; +$AC['disable-phpt'] = false; +$AC['only-phpt'] = false; // alias for --type=phpt + +$aliases = array( + 'f' => 'file', + 'h' => 'help', + 'v' => 'verbose', +); + +// It's important that this does not call the autoloader. Not a problem +// with a function, but could be if we put this in a class. +htmlpurifier_parse_args($AC, $aliases); + +if ($AC['help']) { +?>HTML Purifier test suite +Allowed options: + --flush + --standalone + --file (-f) HTMLPurifier/NameOfTest.php + --xml + --txt + --dry + --php /path/to/php + --type ( htmlpurifier | configdoc | fstools | htmlt | vtest | phpt ) + --disable-phpt + --verbose (-v) +<?php + exit; +} + +// Disable PHPT tests if they're not enabled +if (!$GLOBALS['HTMLPurifierTest']['PHPT']) { + $AC['disable-phpt'] = true; +} elseif (!$AC['type'] && $AC['only-phpt']) { + // backwards-compat + $AC['type'] = 'phpt'; +} + +if (!SimpleReporter::inCli()) { + // Undo any dangerous parameters + $AC['php'] = $php; +} + +// initialize and load HTML Purifier +// use ?standalone to load the alterative standalone stub +if ($AC['standalone']) { + require '../library/HTMLPurifier.standalone.php'; +} else { + require '../library/HTMLPurifier.path.php'; + require 'HTMLPurifier.includes.php'; +} +require '../library/HTMLPurifier.autoload.php'; +require 'HTMLPurifier/Harness.php'; + +// immediately load external libraries, so we can bail out early if +// they're bad +if ($GLOBALS['HTMLPurifierTest']['PEAR']) { + if ($GLOBALS['HTMLPurifierTest']['Net_IDNA2']) { + require_once 'Net/IDNA2.php'; + } +} + +// Shell-script code is executed + +if ($AC['xml']) { + if (!SimpleReporter::inCli()) header('Content-Type: text/xml;charset=UTF-8'); + $reporter = new XmlReporter(); +} elseif (SimpleReporter::inCli() || $AC['txt']) { + if (!SimpleReporter::inCli()) header('Content-Type: text/plain;charset=UTF-8'); + $reporter = new HTMLPurifier_SimpleTest_TextReporter($AC); +} else { + $reporter = new HTMLPurifier_SimpleTest_Reporter('UTF-8', $AC); +} + +if ($AC['flush']) { + htmlpurifier_flush($AC['php'], $reporter); +} + +// Now, userland code begins to be executed + +// setup special DefinitionCacheFactory decorator +$factory = HTMLPurifier_DefinitionCacheFactory::instance(); +$factory->addDecorator('Memory'); // since we deal with a lot of config objects + +if (!$AC['disable-phpt']) { + $phpt = PHPT_Registry::getInstance(); + $phpt->php = $AC['php']; +} + +// load tests +require 'test_files.php'; + +$FS = new FSTools(); + +// handle test dirs +foreach ($test_dirs as $dir) { + $raw_files = $FS->globr($dir, '*Test.php'); + foreach ($raw_files as $file) { + $file = str_replace('\\', '/', $file); + if (isset($test_dirs_exclude[$file])) continue; + $test_files[] = $file; + } +} + +// handle vtest dirs +foreach ($vtest_dirs as $dir) { + $raw_files = $FS->globr($dir, '*.vtest'); + foreach ($raw_files as $file) { + $test_files[] = str_replace('\\', '/', $file); + } +} + +// handle phpt files +foreach ($phpt_dirs as $dir) { + $phpt_files = $FS->globr($dir, '*.phpt'); + foreach ($phpt_files as $file) { + $test_files[] = str_replace('\\', '/', $file); + } +} + +// handle htmlt dirs +foreach ($htmlt_dirs as $dir) { + $htmlt_files = $FS->globr($dir, '*.htmlt'); + foreach ($htmlt_files as $file) { + $test_files[] = str_replace('\\', '/', $file); + } +} + +array_unique($test_files); +sort($test_files); // for the SELECT +$GLOBALS['HTMLPurifierTest']['Files'] = $test_files; // for the reporter +$test_file_lookup = array_flip($test_files); + +// determine test file +if ($AC['file']) { + if (!isset($test_file_lookup[$AC['file']])) { + echo "Invalid file passed\n"; + exit; + } +} + +if ($AC['file']) { + + $test = new TestSuite($AC['file']); + htmlpurifier_add_test($test, $AC['file']); + +} else { + + $standalone = ''; + if ($AC['standalone']) $standalone = ' (standalone)'; + $test = new TestSuite('All HTML Purifier tests on PHP ' . PHP_VERSION . $standalone); + foreach ($test_files as $test_file) { + htmlpurifier_add_test($test, $test_file); + } + +} + +if ($AC['dry']) $reporter->makeDry(); + +$test->run($reporter); + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/multitest.php b/lib/htmlpurifier/tests/multitest.php new file mode 100644 index 000000000..ef296cdda --- /dev/null +++ b/lib/htmlpurifier/tests/multitest.php @@ -0,0 +1,159 @@ +<?php + +/** @file + * Multiple PHP Versions test + * + * This file tests HTML Purifier in all versions of PHP. Arguments + * are specified like --arg=opt, allowed arguments are: + * - quiet (q), if specified no informative messages are enabled (please use + * this if you're outputting XML) + * - distro, allowed values 'normal' or 'standalone', by default all + * distributions are tested. "--standalone" is a shortcut for + * "--distro=standalone". + * - quick, run only the most recent versions of each release series + * - disable-flush, by default flush is run, this disables it + * - file (f), xml, type: these correspond to the parameters in index.php + * + * @note + * It requires a script called phpv that takes an extra argument (the + * version number of PHP) before all other arguments. Contact me if you'd + * like to set up a similar script. The name of the script can be + * edited with $phpv + * + * @note + * Also, configuration must be set up with a variable called + * $versions_to_test specifying version numbers to pass to $phpv + */ + +define('HTMLPurifierTest', 1); +chdir(dirname(__FILE__)); +$php = 'php'; // for safety + +require_once 'common.php'; + +if (!SimpleReporter::inCli()) { + echo 'Multitest only available from command line'; + exit; +} + +$AC = array(); // parameters +$AC['file'] = ''; +$AC['xml'] = false; +$AC['quiet'] = false; +$AC['php'] = $php; +$AC['disable-phpt'] = false; +$AC['disable-flush'] = false; +$AC['type'] = ''; +$AC['distro'] = ''; // valid values are normal/standalone +$AC['quick'] = false; // run the latest version on each release series +$AC['standalone'] = false; // convenience for --distro=standalone +// Legacy parameters +$AC['only-phpt'] = false; // --type=phpt +$AC['exclude-normal'] = false; // --distro=standalone +$AC['exclude-standalone'] = false; // --distro=normal +$AC['verbose'] = false; +$aliases = array( + 'f' => 'file', + 'q' => 'quiet', + 'v' => 'verbose', +); +htmlpurifier_parse_args($AC, $aliases); + +// Backwards compat extra parsing +if ($AC['only-phpt']) { + $AC['type'] = 'phpt'; +} +if ($AC['exclude-normal']) $AC['distro'] = 'standalone'; +elseif ($AC['exclude-standalone']) $AC['distro'] = 'normal'; +elseif ($AC['standalone']) $AC['distro'] = 'standalone'; + +if ($AC['xml']) { + $reporter = new XmlReporter(); +} else { + $reporter = new HTMLPurifier_SimpleTest_TextReporter($AC); +} + +// Regenerate any necessary files +if (!$AC['disable-flush']) htmlpurifier_flush($AC['php'], $reporter); + +$file_arg = ''; +require 'test_files.php'; +if ($AC['file']) { + $test_files_lookup = array_flip($test_files); + if (isset($test_files_lookup[$AC['file']])) { + $file_arg = '--file=' . $AC['file']; + } else { + throw new Exception("Invalid file passed"); + } +} +// This allows us to get out of having to do dry runs. +$size = count($test_files); + +$type_arg = ''; +if ($AC['type']) $type_arg = '--type=' . $AC['type']; + +if ($AC['quick']) { + $seriesArray = array(); + foreach ($versions_to_test as $version) { + $series = substr($version, 0, strpos($version, '.', strpos($version, '.') + 1)); + if (!isset($seriesArray[$series])) { + $seriesArray[$series] = $version; + continue; + } + if (version_compare($version, $seriesArray[$series], '>')) { + $seriesArray[$series] = $version; + } + } + $versions_to_test = array_values($seriesArray); +} + +// Setup the test +$test = new TestSuite('HTML Purifier Multiple Versions Test'); +foreach ($versions_to_test as $version) { + // Support for arbitrarily forcing flushes by wrapping the suspect + // version name in an array() + $flush_arg = ''; + if (is_array($version)) { + $version = $version[0]; + $flush_arg = '--flush'; + } + if ($AC['type'] !== 'phpt') { + $break = true; + switch ($AC['distro']) { + case '': + $break = false; + case 'normal': + $test->add( + new CliTestCase( + "$phpv $version index.php --xml $flush_arg $type_arg --disable-phpt $file_arg", + $AC['quiet'], $size + ) + ); + if ($break) break; + case 'standalone': + $test->add( + new CliTestCase( + "$phpv $version index.php --xml $flush_arg $type_arg --standalone --disable-phpt $file_arg", + $AC['quiet'], $size + ) + ); + if ($break) break; + } + } + if (!$AC['disable-phpt'] && (!$AC['type'] || $AC['type'] == 'phpt')) { + $test->add( + new CliTestCase( + $AC['php'] . " index.php --xml --php \"$phpv $version\" --type=phpt", + $AC['quiet'], $size + ) + ); + } +} + +// This is the HTML Purifier website's test XML file. We could +// add more websites, i.e. more configurations to test. +// $test->add(new RemoteTestCase('http://htmlpurifier.org/dev/tests/?xml=1', 'http://htmlpurifier.org/dev/tests/?xml=1&dry=1&flush=1')); + +$test->run($reporter); + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/path2class.func.php b/lib/htmlpurifier/tests/path2class.func.php new file mode 100644 index 000000000..95a3a95ae --- /dev/null +++ b/lib/htmlpurifier/tests/path2class.func.php @@ -0,0 +1,14 @@ +<?php + +function path2class($path) { + $temp = $path; + $temp = str_replace('./', '', $temp); // remove leading './' + $temp = str_replace('.\\', '', $temp); // remove leading '.\' + $temp = str_replace('\\', '_', $temp); // normalize \ to _ + $temp = str_replace('/', '_', $temp); // normalize / to _ + while(strpos($temp, '__') !== false) $temp = str_replace('__', '_', $temp); + $temp = str_replace('.php', '', $temp); + return $temp; +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/test_files.php b/lib/htmlpurifier/tests/test_files.php new file mode 100644 index 000000000..d2b86cdb4 --- /dev/null +++ b/lib/htmlpurifier/tests/test_files.php @@ -0,0 +1,45 @@ +<?php + +if (!defined('HTMLPurifierTest')) exit; + +// These arrays are defined by this file and can be relied upon. +$test_files = array(); +$test_dirs = array(); +$test_dirs_exclude = array(); +$vtest_dirs = array(); +$htmlt_dirs = array(); +$phpt_dirs = array(); + +$break = true; +switch ($AC['type']) { + case '': + $break = false; + case 'htmlpurifier': + $test_dirs[] = 'HTMLPurifier'; + $test_files[] = 'HTMLPurifierTest.php'; + $test_dirs_exclude['HTMLPurifier/Filter/ExtractStyleBlocksTest.php'] = true; + if ($csstidy_location) { + $test_files[] = 'HTMLPurifier/Filter/ExtractStyleBlocksTest.php'; + } + if ($break) break; + case 'configdoc': + if (version_compare(PHP_VERSION, '5.2', '>=')) { + // $test_dirs[] = 'ConfigDoc'; // no test files currently! + } + if ($break) break; + case 'fstools': + $test_dirs[] = 'FSTools'; + case 'htmlt': + $htmlt_dirs[] = 'HTMLPurifier/HTMLT'; + if ($break) break; + case 'vtest': + $vtest_dirs[] = 'HTMLPurifier/ConfigSchema/Validator'; + if ($break) break; + + case 'phpt': + if (!$AC['disable-phpt'] && version_compare(PHP_VERSION, '5.2', '>=')) { + $phpt_dirs[] = 'HTMLPurifier/PHPT'; + } +} + +// vim: et sw=4 sts=4 diff --git a/lib/htmlpurifier/tests/tmp/README b/lib/htmlpurifier/tests/tmp/README new file mode 100644 index 000000000..2e35c1c3d --- /dev/null +++ b/lib/htmlpurifier/tests/tmp/README @@ -0,0 +1,3 @@ +This is a dummy file to prevent Git from ignoring this empty directory. + + vim: et sw=4 sts=4 |