From 13eb016e063a8e0c8d68ef192f286b7159c3ca5e Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Tue, 10 Jun 2008 00:13:44 +0000 Subject: [PATCH] [3.1.1] Implement SafeObject. git-svn-id: http://htmlpurifier.org/svnroot/htmlpurifier/trunk@1780 48356398-32a2-884e-a903-53898d9a118a --- NEWS | 1 + TODO | 3 +- configdoc/usage.xml | 37 +++++----- library/HTMLPurifier.includes.php | 4 ++ library/HTMLPurifier.safe-includes.php | 4 ++ library/HTMLPurifier/AttrTransform/SafeObject.php | 14 ++++ library/HTMLPurifier/AttrTransform/SafeParam.php | 48 +++++++++++++ library/HTMLPurifier/HTMLModule/SafeObject.php | 48 +++++++++++++ library/HTMLPurifier/Injector/SafeObject.php | 77 ++++++++++++++++++++ library/HTMLPurifier/Token.php | 1 - tests/HTMLPurifier/HTMLModule/SafeObjectTest.php | 42 +++++++++++ tests/HTMLPurifier/Injector/SafeObjectTest.php | 86 +++++++++++++++++++++++ tests/index.php | 1 + 13 files changed, 346 insertions(+), 20 deletions(-) create mode 100644 library/HTMLPurifier/AttrTransform/SafeObject.php create mode 100644 library/HTMLPurifier/AttrTransform/SafeParam.php create mode 100644 library/HTMLPurifier/HTMLModule/SafeObject.php create mode 100644 library/HTMLPurifier/Injector/SafeObject.php create mode 100644 tests/HTMLPurifier/HTMLModule/SafeObjectTest.php create mode 100644 tests/HTMLPurifier/Injector/SafeObjectTest.php diff --git a/NEWS b/NEWS index e34a4acd..0945fccb 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier a URIFilter as such. ! Allow modules to define injectors via $info_injector. Injectors are automatically disabled if injector's needed elements are not found. +! Support for "safe" objects added [info on how to enable needed] - Disable percent height/width attributes for img - AttrValidator operations are now atomic; updates to attributes are not manifest in token until end of operations. This prevents naughty internal diff --git a/TODO b/TODO index b5c8dcc6..27383eaa 100644 --- a/TODO +++ b/TODO @@ -24,11 +24,10 @@ FUTURE VERSIONS 3.2 release [It's All About Trust] (floating) # Implement untrusted, dangerous elements/attributes - - Objects and Forms are especially wanted + - Forms are especially wanted # Implement IDREF support (harder than it seems, since you cannot have IDREFs to non-existent IDs) # Frameset XHTML 1.0 and HTML 4.01 doctypes - - Research and implement a "safe" version of the Object module 3.3 release [Error'ed] # Error logging for filtering/cleanup procedures diff --git a/configdoc/usage.xml b/configdoc/usage.xml index c5f149ac..fca26836 100644 --- a/configdoc/usage.xml +++ b/configdoc/usage.xml @@ -68,19 +68,19 @@ - 288 - 315 + 267 + 294 - 293 - 323 + 272 + 302 - 319 + 298 @@ -111,37 +111,37 @@ - 213 + 222 - 221 + 230 - 238 + 247 - 239 + 248 - 242 + 251 - 328 + 337 - 329 + 338 @@ -195,7 +195,7 @@ - 63 + 64 8 @@ -203,12 +203,12 @@ - 64 + 65 - 71 + 72 @@ -223,12 +223,12 @@ - 23 + 28 - 72 + 77 @@ -317,6 +317,9 @@ 14 + + 19 + diff --git a/library/HTMLPurifier.includes.php b/library/HTMLPurifier.includes.php index 7fc82f2d..9ea8f973 100644 --- a/library/HTMLPurifier.includes.php +++ b/library/HTMLPurifier.includes.php @@ -118,6 +118,8 @@ require 'HTMLPurifier/AttrTransform/ImgSpace.php'; require 'HTMLPurifier/AttrTransform/Lang.php'; require 'HTMLPurifier/AttrTransform/Length.php'; require 'HTMLPurifier/AttrTransform/Name.php'; +require 'HTMLPurifier/AttrTransform/SafeObject.php'; +require 'HTMLPurifier/AttrTransform/SafeParam.php'; require 'HTMLPurifier/AttrTransform/ScriptRequired.php'; require 'HTMLPurifier/ChildDef/Chameleon.php'; require 'HTMLPurifier/ChildDef/Custom.php'; @@ -143,6 +145,7 @@ require 'HTMLPurifier/HTMLModule/Object.php'; require 'HTMLPurifier/HTMLModule/Presentation.php'; require 'HTMLPurifier/HTMLModule/Proprietary.php'; require 'HTMLPurifier/HTMLModule/Ruby.php'; +require 'HTMLPurifier/HTMLModule/SafeObject.php'; require 'HTMLPurifier/HTMLModule/Scripting.php'; require 'HTMLPurifier/HTMLModule/StyleAttribute.php'; require 'HTMLPurifier/HTMLModule/Tables.php'; @@ -158,6 +161,7 @@ require 'HTMLPurifier/HTMLModule/Tidy/XHTML.php'; require 'HTMLPurifier/Injector/AutoParagraph.php'; require 'HTMLPurifier/Injector/Linkify.php'; require 'HTMLPurifier/Injector/PurifierLinkify.php'; +require 'HTMLPurifier/Injector/SafeObject.php'; require 'HTMLPurifier/Lexer/DOMLex.php'; require 'HTMLPurifier/Lexer/DirectLex.php'; require 'HTMLPurifier/Strategy/Composite.php'; diff --git a/library/HTMLPurifier.safe-includes.php b/library/HTMLPurifier.safe-includes.php index 3bda31d6..c775ed00 100644 --- a/library/HTMLPurifier.safe-includes.php +++ b/library/HTMLPurifier.safe-includes.php @@ -112,6 +112,8 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/ImgSpace.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php'; +require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php'; +require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php'; @@ -137,6 +139,7 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php'; +require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php'; @@ -152,6 +155,7 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTML.php'; require_once $__dir . '/HTMLPurifier/Injector/AutoParagraph.php'; require_once $__dir . '/HTMLPurifier/Injector/Linkify.php'; require_once $__dir . '/HTMLPurifier/Injector/PurifierLinkify.php'; +require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php'; require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php'; require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php'; require_once $__dir . '/HTMLPurifier/Strategy/Composite.php'; diff --git a/library/HTMLPurifier/AttrTransform/SafeObject.php b/library/HTMLPurifier/AttrTransform/SafeObject.php new file mode 100644 index 00000000..dda60e5d --- /dev/null +++ b/library/HTMLPurifier/AttrTransform/SafeObject.php @@ -0,0 +1,14 @@ +uri = new HTMLPurifier_AttrDef_URI(true); // embedded + } + + function transform($attr, $config, $context) { + // If we add support for other objects, we'll need to alter the + // transforms. + switch ($attr['name']) { + // application/x-shockwave-flash + // Keep this synchronized with Injector/SafeObject.php + case 'allowScriptAccess': + $attr['value'] = 'never'; + break; + case 'allowNetworking': + $attr['value'] = 'internal'; + break; + case 'wmode': + $attr['value'] = 'window'; + break; + case 'movie': + $attr['value'] = $this->uri->validate($attr['value'], $config, $context); + break; + // add other cases to support other param name/value pairs + default: + $attr['name'] = $attr['value'] = null; + } + return $attr; + } +} diff --git a/library/HTMLPurifier/HTMLModule/SafeObject.php b/library/HTMLPurifier/HTMLModule/SafeObject.php new file mode 100644 index 00000000..14f8cf22 --- /dev/null +++ b/library/HTMLPurifier/HTMLModule/SafeObject.php @@ -0,0 +1,48 @@ +get('HTML', 'MaxImgLength'); + $object = $this->addElement( + 'object', + 'Inline', + 'Optional: param | Flow | #PCDATA', + 'Common', + array( + // While technically not required by the spec, we're forcing + // it to this value. + 'type' => 'Enum#application/x-shockwave-flash', + 'width' => 'Pixels#' . $max, + 'height' => 'Pixels#' . $max, + 'data' => 'Text' + ) + ); + $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject(); + + $param = $this->addElement('param', false, 'Empty', false, + array( + 'id' => 'ID', + 'name*' => 'Text', + 'value' => 'Text' + ) + ); + $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam(); + $this->info_injector[] = 'SafeObject'; + + } + +} diff --git a/library/HTMLPurifier/Injector/SafeObject.php b/library/HTMLPurifier/Injector/SafeObject.php new file mode 100644 index 00000000..b88bcbcc --- /dev/null +++ b/library/HTMLPurifier/Injector/SafeObject.php @@ -0,0 +1,77 @@ + 'never', + 'allowNetworking' => 'internal', + ); + protected $allowedParam = array( + 'wmode' => true, + 'movie' => true, + ); + + public function prepare($config, $context) { + parent::prepare($config, $context); + } + + public function handleElement(&$token) { + if ($token->name == 'object') { + $this->objectStack[] = $token; + $this->paramStack[] = array(); + $new = array($token); + foreach ($this->addParam as $name => $value) { + $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value)); + } + $token = $new; + } elseif ($token->name == 'param') { + $nest = count($this->currentNesting) - 1; + if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') { + $i = count($this->objectStack) - 1; + if (!isset($token->attr['name'])) { + $token = false; + return; + } + $n = $token->attr['name']; + // Check if the parameter is the correct value but has not + // already been added + if ( + !isset($this->paramStack[$i][$n]) && + isset($this->addParam[$n]) && + $token->attr['name'] === $this->addParam[$n] + ) { + // keep token, and add to param stack + $this->paramStack[$i][$n] = true; + } elseif (isset($this->allowedParam[$n])) { + // keep token, don't do anything to it + // (could possibly check for duplicates here) + } else { + $token = false; + } + } else { + // not directly inside an object, DENY! + $token = false; + } + } + } + + public function notifyEnd($token) { + if ($token->name == 'object') { + array_pop($this->objectStack); + array_pop($this->paramStack); + } + } + +} + diff --git a/library/HTMLPurifier/Token.php b/library/HTMLPurifier/Token.php index fd2ba53f..8803307b 100644 --- a/library/HTMLPurifier/Token.php +++ b/library/HTMLPurifier/Token.php @@ -4,7 +4,6 @@ * Abstract base token class that all others inherit from. */ class HTMLPurifier_Token { - public $type; /**< Type of node to bypass is_a(). */ public $line; /**< Line number node was on in source document. Null if unknown. */ /** diff --git a/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php b/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php new file mode 100644 index 00000000..d2db2460 --- /dev/null +++ b/tests/HTMLPurifier/HTMLModule/SafeObjectTest.php @@ -0,0 +1,42 @@ +config->set('HTML', 'DefinitionID', 'HTMLPurifier_HTMLModule_SafeObjectTest'); + $def = $this->config->getHTMLDefinition(true); + $def->manager->addModule('SafeObject'); + } + + function testMinimal() { + $this->assertResult( + '', + '' + ); + } + + function testYouTube() { + // embed is purposely removed + $this->assertResult( + '', + '' + ); + } + + function testMalicious() { + $this->assertResult( + '', + '' + ); + } + + function testFull() { + $this->assertResult( + '' + ); + } + +} + diff --git a/tests/HTMLPurifier/Injector/SafeObjectTest.php b/tests/HTMLPurifier/Injector/SafeObjectTest.php new file mode 100644 index 00000000..7a6cbb0d --- /dev/null +++ b/tests/HTMLPurifier/Injector/SafeObjectTest.php @@ -0,0 +1,86 @@ +config->set('AutoFormat', 'Custom', array(new HTMLPurifier_Injector_SafeObject())); + $this->config->set('HTML', 'Trusted', true); + } + + function testPreserve() { + $this->assertResult( + 'asdf' + ); + } + + function testRemoveStrayParam() { + $this->assertResult( + '', + '' + ); + } + + function testEditObjectParam() { + $this->assertResult( + '', + '' + ); + } + + function testIgnoreStrayParam() { + $this->assertResult( + '', + '' + ); + } + + function testIgnoreDuplicates() { + $this->assertResult( + '' + ); + } + + function testIgnoreBogusData() { + $this->assertResult( + '', + '' + ); + } + + function testIgnoreInvalidData() { + $this->assertResult( + '', + '' + ); + } + + function testKeepValidData() { + $this->assertResult( + '', + '' + ); + } + + function testNested() { + $this->assertResult( + '', + '' + ); + } + + function testNotActuallyNested() { + $this->assertResult( + '

', + '

' + ); + } + +} + diff --git a/tests/index.php b/tests/index.php index 4886732d..c3b15694 100755 --- a/tests/index.php +++ b/tests/index.php @@ -26,6 +26,7 @@ define('HTMLPURIFIER_SCHEMA_STRICT', true); // validate schemas chdir(dirname(__FILE__)); $php = 'php'; // for safety +ini_set('memory_limit', '64M'); require 'common.php'; -- 2.11.4.GIT