From 24cde9c8919c64f0600b5d901623f88af5f7c7ce Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Sun, 27 Aug 2006 18:49:16 +0000 Subject: [PATCH] Revamp configuration files so that more rules can be added, internal organization is more logical, and descriptions are captured. git-svn-id: http://htmlpurifier.org/svnroot/htmlpurifier/trunk@327 48356398-32a2-884e-a903-53898d9a118a --- docs/naming.txt | 5 +- library/HTMLPurifier.php | 20 ++ library/HTMLPurifier/AttrDef/URI.php | 2 +- library/HTMLPurifier/AttrTransform/BdoDir.php | 5 +- .../AttrTransform/ImgRequired.php | 4 +- library/HTMLPurifier/ChildDef.php | 2 +- library/HTMLPurifier/Config.php | 31 +- library/HTMLPurifier/ConfigDef.php | 243 +++++++++++++++- library/HTMLPurifier/Generator.php | 2 +- library/HTMLPurifier/Lexer.php | 2 +- library/HTMLPurifier/Strategy.php | 2 +- .../Strategy/ValidateAttributes.php | 2 +- library/HTMLPurifier/URISchemeRegistry.php | 4 +- tests/HTMLPurifier/ConfigDefTest.php | 269 ++++++++++++++++-- tests/HTMLPurifier/ConfigTest.php | 61 +++- 15 files changed, 600 insertions(+), 54 deletions(-) diff --git a/docs/naming.txt b/docs/naming.txt index ff2b82b9..2be60698 100644 --- a/docs/naming.txt +++ b/docs/naming.txt @@ -6,8 +6,9 @@ help you find the correct functionality more quickly. Here they are: All classes occupy the HTMLPurifier pseudo-namespace. This means that all classes are prefixed with HTMLPurifier_. As such, all - names under HTMLPurifier_ are reserved, and userspace extensions should - be registered in a different namespace (or the main namespace). + names under HTMLPurifier_ are reserved. I recommend that you use the name + HTMLPurifierX_YourName_ClassName, especially if you want to take advantage + of HTMLPurifier_ConfigDef. All classes correspond to their path if library/ was in the include path HTMLPurifier_AttrDef is located at HTMLPurifier/AttrDef.php; replace diff --git a/library/HTMLPurifier.php b/library/HTMLPurifier.php index 7596b0ed..0249daf8 100644 --- a/library/HTMLPurifier.php +++ b/library/HTMLPurifier.php @@ -28,6 +28,26 @@ require_once 'HTMLPurifier/HTMLDefinition.php'; require_once 'HTMLPurifier/Generator.php'; require_once 'HTMLPurifier/Strategy/Core.php'; +HTMLPurifier_ConfigDef::define( + 'Core', 'Encoding', 'utf-8', 'istring', + 'Defines the input and output character encodings to use. HTMLPurifier '. + 'internally uses UTF-8, making that the painless default choice. Note '. + 'certain implementations of HTMLPurifier_Lexer are intelligent enough '. + 'automatically detect encoding, however, output format will always be '. + 'this value.' +); +HTMLPurifier_ConfigDef::defineAllowedValues( + 'Core', 'Encoding', array( + 'utf-8', + 'iso-8859-1' + ) +); +HTMLPurifier_ConfigDef::defineValueAliases( + 'Core', 'Encoding', array( + 'iso8859-1' => 'iso-8859-1' + ) +); + /** * Main library execution class. * diff --git a/library/HTMLPurifier/AttrDef/URI.php b/library/HTMLPurifier/AttrDef/URI.php index 4a48d7b1..76d665fe 100644 --- a/library/HTMLPurifier/AttrDef/URI.php +++ b/library/HTMLPurifier/AttrDef/URI.php @@ -6,7 +6,7 @@ require_once 'HTMLPurifier/URISchemeRegistry.php'; require_once 'HTMLPurifier/AttrDef/Host.php'; HTMLPurifier_ConfigDef::define( - 'URI', 'DefaultScheme', 'http', + 'URI', 'DefaultScheme', 'http', 'string', 'Defines through what scheme the output will be served, in order to '. 'select the proper object validator when no scheme information is present.' ); diff --git a/library/HTMLPurifier/AttrTransform/BdoDir.php b/library/HTMLPurifier/AttrTransform/BdoDir.php index 442eface..5f2f4fe2 100644 --- a/library/HTMLPurifier/AttrTransform/BdoDir.php +++ b/library/HTMLPurifier/AttrTransform/BdoDir.php @@ -5,11 +5,14 @@ require_once 'HTMLPurifier/AttrTransform.php'; // this MUST be placed in post, as it assumes that any value in dir is valid HTMLPurifier_ConfigDef::define( - 'Attr', 'DefaultTextDir', 'ltr', + 'Attr', 'DefaultTextDir', 'ltr', 'string', 'Defines the default text direction (ltr or rtl) of the document '. 'being parsed. This generally is the same as the value of the dir '. 'attribute in HTML, or ltr if that is not specified.' ); +HTMLPurifier_ConfigDef::defineAllowedValues( + 'Attr', 'DefaultTextDir', array( 'ltr', 'rtl' ) +); /** * Post-trasnform that ensures that bdo tags have the dir attribute set. diff --git a/library/HTMLPurifier/AttrTransform/ImgRequired.php b/library/HTMLPurifier/AttrTransform/ImgRequired.php index f43ddef7..bafe90b4 100644 --- a/library/HTMLPurifier/AttrTransform/ImgRequired.php +++ b/library/HTMLPurifier/AttrTransform/ImgRequired.php @@ -5,7 +5,7 @@ require_once 'HTMLPurifier/AttrTransform.php'; // must be called POST validation HTMLPurifier_ConfigDef::define( - 'Attr', 'DefaultInvalidImage', '', + 'Attr', 'DefaultInvalidImage', '', 'string', 'This is the default image an img tag will be pointed to if it does '. 'not have a valid src attribute. In future versions, we may allow the '. 'image tag to be removed completely, but due to design issues, this is '. @@ -13,7 +13,7 @@ HTMLPurifier_ConfigDef::define( ); HTMLPurifier_ConfigDef::define( - 'Attr', 'DefaultInvalidImageAlt', 'Invalid image', + 'Attr', 'DefaultInvalidImageAlt', 'Invalid image', 'string', 'This is the content of the alt tag of an invalid image if the user '. 'had not previously specified an alt attribute. It has no effect when the '. 'image is valid but there was no alt attribute present.' diff --git a/library/HTMLPurifier/ChildDef.php b/library/HTMLPurifier/ChildDef.php index 6df69846..91bda67e 100644 --- a/library/HTMLPurifier/ChildDef.php +++ b/library/HTMLPurifier/ChildDef.php @@ -13,7 +13,7 @@ // in order to make it self correcting HTMLPurifier_ConfigDef::define( - 'Core', 'EscapeInvalidChildren', false, + 'Core', 'EscapeInvalidChildren', false, 'bool', 'When true, a child is found that is not allowed in the context of the '. 'parent element will be transformed into text as if it were ASCII. When '. 'false, that element and all internal tags will be dropped, though text '. diff --git a/library/HTMLPurifier/Config.php b/library/HTMLPurifier/Config.php index 01dd605e..52a39362 100644 --- a/library/HTMLPurifier/Config.php +++ b/library/HTMLPurifier/Config.php @@ -20,12 +20,18 @@ class HTMLPurifier_Config */ var $conf; + /** + * Reference HTMLPurifier_ConfigDef for value checking + */ + var $def; + /** * @param $definition HTMLPurifier_ConfigDef that defines what directives * are allowed. */ function HTMLPurifier_Config(&$definition) { - $this->conf = $definition->info; // set up the defaults + $this->conf = $definition->defaults; // set up, copy in defaults + $this->def = $definition; // keep a copy around for checking } /** @@ -46,7 +52,7 @@ class HTMLPurifier_Config function get($namespace, $key) { if (!isset($this->conf[$namespace][$key])) { trigger_error('Cannot retrieve value of undefined directive', - E_USER_ERROR); + E_USER_WARNING); return; } return $this->conf[$namespace][$key]; @@ -61,7 +67,26 @@ class HTMLPurifier_Config function set($namespace, $key, $value) { if (!isset($this->conf[$namespace][$key])) { trigger_error('Cannot set undefined directive to value', - E_USER_ERROR); + E_USER_WARNING); + return; + } + if (is_string($value)) { + // resolve value alias if defined + if (isset($this->def->info[$namespace][$key]->aliases[$value])) { + $value = $this->def->info[$namespace][$key]->aliases[$value]; + } + if ($this->def->info[$namespace][$key]->allowed !== true) { + // check to see if the value is allowed + if (!isset($this->def->info[$namespace][$key]->allowed[$value])) { + trigger_error('Value not supported', E_USER_WARNING); + return; + } + } + } + $value = $this->def->validate($value, + $this->def->info[$namespace][$key]->type); + if ($value === null) { + trigger_error('Value is of invalid type', E_USER_WARNING); return; } $this->conf[$namespace][$key] = $value; diff --git a/library/HTMLPurifier/ConfigDef.php b/library/HTMLPurifier/ConfigDef.php index d1f253f7..3607450a 100644 --- a/library/HTMLPurifier/ConfigDef.php +++ b/library/HTMLPurifier/ConfigDef.php @@ -7,11 +7,36 @@ class HTMLPurifier_ConfigDef { /** - * Currently defined directives (and namespaces). + * Defaults of the directives and namespaces. * @note This shares the exact same structure as HTMLPurifier_Config::$conf */ + var $defaults = array(); + + /** + * Definition of the directives. + */ var $info = array(); + /** + * Definition of namespaces. + */ + var $info_namespace = array(); + + /** + * Lookup table of allowed types. + */ + var $types = array( + 'string' => true, + 'istring' => true, + 'int' => true, + 'float' => true, + 'bool' => true, + 'lookup' => true, + 'list' => true, + 'hash' => true, + 'mixed' => true + ); + /** * Initializes the default namespaces. */ @@ -44,9 +69,14 @@ class HTMLPurifier_ConfigDef { * @param $namespace Namespace the directive is in * @param $name Key of directive * @param $default Default value of directive + * @param $type Allowed type of the directive. See + * HTMLPurifier_DirectiveDef::$type for allowed values * @param $description Description of directive for documentation */ - function define($namespace, $name, $default, $description) { + function define( + $namespace, $name, $default, $type, + $description + ) { $def =& HTMLPurifier_ConfigDef::instance(); if (!isset($def->info[$namespace])) { trigger_error('Cannot define directive for undefined namespace', @@ -54,11 +84,33 @@ class HTMLPurifier_ConfigDef { return; } if (isset($def->info[$namespace][$name])) { - // this behavior is at risk of change - trigger_error('Cannot redefine directive', E_USER_ERROR); - return; + if ( + $def->info[$namespace][$name]->type !== $type || + $def->defaults[$namespace][$name] !== $default + ) { + trigger_error('Inconsistent default or type, cannot redefine'); + return; + } + } else { + if (!isset($def->types[$type])) { + trigger_error('Invalid type for configuration directive', + E_USER_ERROR); + return; + } + if ($def->validate($default, $type) === null) { + trigger_error('Default value does not match directive type', + E_USER_ERROR); + return; + } + $def->info[$namespace][$name] = + new HTMLPurifier_ConfigEntity_Directive(); + $def->info[$namespace][$name]->type = $type; + $def->defaults[$namespace][$name] = $default; } - $def->info[$namespace][$name] = $default; + $backtrace = debug_backtrace(); + $file = $def->mungeFilename($backtrace[0]['file']); + $line = $backtrace[0]['line']; + $def->info[$namespace][$name]->addDescription($file,$line,$description); } /** @@ -73,8 +125,187 @@ class HTMLPurifier_ConfigDef { return; } $def->info[$namespace] = array(); + $def->info_namespace[$namespace] = new HTMLPurifier_ConfigEntity_Namespace(); + $backtrace = debug_backtrace(); + $file = $def->mungeFilename($backtrace[0]['file']); + $line = $backtrace[0]['line']; + $def->info_namespace[$namespace]->addDescription($file,$line,$description); + $def->defaults[$namespace] = array(); + } + + /** + * Defines a directive value alias. + * + * Directive value aliases are convenient for developers because it lets + * them set a directive to several values and get the same result. + * @param $namespace Directive's namespace + * @param $name Name of Directive + * @param $alias Name of aliased value + * @param $real Value aliased value will be converted into + */ + function defineValueAliases($namespace, $name, $aliases) { + $def =& HTMLPurifier_ConfigDef::instance(); + if (!isset($def->info[$namespace][$name])) { + trigger_error('Cannot set value alias for non-existant directive', + E_USER_ERROR); + return; + } + foreach ($aliases as $alias => $real) { + if (!$def->info[$namespace][$name] !== true && + !isset($def->info[$namespace][$name]->allowed[$real]) + ) { + trigger_error('Cannot define alias to value that is not allowed', + E_USER_ERROR); + return; + } + if (isset($def->info[$namespace][$name]->allowed[$alias])) { + trigger_error('Cannot define alias over allowed value', + E_USER_ERROR); + return; + } + $def->info[$namespace][$name]->aliases[$alias] = $real; + } + } + + /** + * Defines a set of allowed values for a directive. + * @param $namespace Namespace of directive + * @param $name Name of directive + * @param $allowed_values Arraylist of allowed values + */ + function defineAllowedValues($namespace, $name, $allowed_values) { + $def =& HTMLPurifier_ConfigDef::instance(); + if (!isset($def->info[$namespace][$name])) { + trigger_error('Cannot define allowed values for undefined directive', + E_USER_ERROR); + return; + } + if ($def->info[$namespace][$name]->allowed === true) { + $def->info[$namespace][$name]->allowed = array(); + } + foreach ($allowed_values as $value) { + $def->info[$namespace][$name]->allowed[$value] = true; + } + } + + /** + * Validate a variable according to type. Return null if invalid. + */ + function validate($var, $type) { + if (!isset($this->types[$type])) { + trigger_error('Invalid type', E_USER_ERROR); + return; + } + switch ($type) { + case 'mixed': + return $var; + case 'istring': + case 'string': + if (!is_string($var)) return; + if ($type === 'istring') $var = strtolower($var); + return $var; + case 'int': + if (is_string($var) && ctype_digit($var)) $var = (int) $var; + elseif (!is_int($var)) return; + return $var; + case 'float': + if (is_string($var) && is_numeric($var)) $var = (float) $var; + elseif (!is_float($var)) return; + return $var; + case 'bool': + if (is_int($var) && ($var === 0 || $var === 1)) { + $var = (bool) $var; + } elseif (!is_bool($var)) return; + return $var; + case 'list': + case 'hash': + case 'lookup': + if (!is_array($var)) return; + $keys = array_keys($var); + if ($keys === array_keys($keys)) { + if ($type == 'list') return $var; + elseif ($type == 'lookup') { + $new = array(); + foreach ($var as $key) { + $new[$key] = true; + } + return $new; + } else return; + } + if ($type === 'lookup') { + foreach ($var as $key => $value) { + $var[$key] = true; + } + } + return $var; + } + } + + function mungeFilename($filename) { + $offset = strrpos($filename, 'HTMLPurifier'); + $filename = substr($filename, $offset); + $filename = str_replace('\\', '/', $filename); + return $filename; } } +/** + * Base class for configuration entity + */ +class HTMLPurifier_ConfigEntity +{ + /** + * Plaintext descriptions of the configuration entity is. Organized by + * file and line number, so multiple descriptions are allowed. + */ + var $descriptions = array(); + + /** + * Adds a description to the array + */ + function addDescription($file, $line, $description) { + if (!isset($this->descriptions[$file])) $this->descriptions[$file] = array(); + $this->descriptions[$file][$line] = $description; + } +} + +/** + * Structure object describing of a namespace + */ +class HTMLPurifier_ConfigEntity_Namespace extends HTMLPurifier_ConfigEntity {} + +/** + * Structure object containing definition of a directive. + * @note This structure does not contain default values + */ +class HTMLPurifier_ConfigEntity_Directive extends HTMLPurifier_ConfigEntity +{ + + /** + * Hash of value aliases, i.e. values that are equivalent. + */ + var $aliases = array(); + + /** + * Lookup table of allowed values of the element, bool true if all allowed. + */ + var $allowed = true; + + /** + * Allowed type of the directive. Values are: + * - string + * - istring (case insensitive string) + * - int + * - float + * - bool + * - lookup (array of value => true) + * - list (regular numbered index array) + * - hash (array of key => value) + * - mixed (anything goes) + */ + var $type = 'mixed'; + +} + ?> \ No newline at end of file diff --git a/library/HTMLPurifier/Generator.php b/library/HTMLPurifier/Generator.php index f2ac78d8..bb366942 100644 --- a/library/HTMLPurifier/Generator.php +++ b/library/HTMLPurifier/Generator.php @@ -5,7 +5,7 @@ require_once 'HTMLPurifier/Lexer.php'; HTMLPurifier_ConfigDef::define( - 'Core', 'CleanUTF8DuringGeneration', false, + 'Core', 'CleanUTF8DuringGeneration', false, 'bool', 'When true, HTMLPurifier_Generator will also check all strings it '. 'escapes for UTF-8 well-formedness as a defense in depth measure. '. 'This could cause a considerable performance impact, and is not '. diff --git a/library/HTMLPurifier/Lexer.php b/library/HTMLPurifier/Lexer.php index 35235658..1f004ea9 100644 --- a/library/HTMLPurifier/Lexer.php +++ b/library/HTMLPurifier/Lexer.php @@ -3,7 +3,7 @@ require_once 'HTMLPurifier/Token.php'; HTMLPurifier_ConfigDef::define( - 'Core', 'AcceptFullDocuments', true, + 'Core', 'AcceptFullDocuments', true, 'bool', 'This parameter determines whether or not the filter should accept full '. 'HTML documents, not just HTML fragments. When on, it will '. 'drop all sections except the content between body. Depending on '. diff --git a/library/HTMLPurifier/Strategy.php b/library/HTMLPurifier/Strategy.php index c875ee4c..dcd683e4 100644 --- a/library/HTMLPurifier/Strategy.php +++ b/library/HTMLPurifier/Strategy.php @@ -9,7 +9,7 @@ */ HTMLPurifier_ConfigDef::define( - 'Core', 'EscapeInvalidTags', false, + 'Core', 'EscapeInvalidTags', false, 'bool', 'When true, invalid tags will be written back to the document as plain '. 'text. Otherwise, they are silently dropped.' ); diff --git a/library/HTMLPurifier/Strategy/ValidateAttributes.php b/library/HTMLPurifier/Strategy/ValidateAttributes.php index 59b3a1ba..dc447ce0 100644 --- a/library/HTMLPurifier/Strategy/ValidateAttributes.php +++ b/library/HTMLPurifier/Strategy/ValidateAttributes.php @@ -7,7 +7,7 @@ require_once 'HTMLPurifier/ConfigDef.php'; require_once 'HTMLPurifier/AttrContext.php'; HTMLPurifier_ConfigDef::define( - 'Attr', 'IDBlacklist', array(), + 'Attr', 'IDBlacklist', array(), 'list', 'Array of IDs not allowed in the document.'); /** diff --git a/library/HTMLPurifier/URISchemeRegistry.php b/library/HTMLPurifier/URISchemeRegistry.php index f68b00cd..da01b360 100644 --- a/library/HTMLPurifier/URISchemeRegistry.php +++ b/library/HTMLPurifier/URISchemeRegistry.php @@ -11,13 +11,13 @@ HTMLPurifier_ConfigDef::define( // for Usenet, these two are similar, but distinct 'nntp' => true, // individual Netnews articles 'news' => true // newsgroup or individual Netnews articles), - ), + ), 'lookup', 'Whitelist that defines the schemes that a URI is allowed to have. This '. 'prevents XSS attacks from using pseudo-schemes like javascript or mocha.' ); HTMLPurifier_ConfigDef::define( - 'URI', 'OverrideAllowedSchemes', true, + 'URI', 'OverrideAllowedSchemes', true, 'bool', 'If this is set to true (which it is by default), you can override '. '%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme '. 'to the registry. If false, you will also have to update that directive '. diff --git a/tests/HTMLPurifier/ConfigDefTest.php b/tests/HTMLPurifier/ConfigDefTest.php index 29bfe268..194d591b 100644 --- a/tests/HTMLPurifier/ConfigDefTest.php +++ b/tests/HTMLPurifier/ConfigDefTest.php @@ -27,38 +27,261 @@ class HTMLPurifier_ConfigDefTest extends UnitTestCase function testNormal() { - HTMLPurifier_ConfigDef::defineNamespace('Core', 'Configuration that '. - 'is always available.'); - $this->assertIdentical( array( - 'Core' => array() - ), $this->our_copy->info); + $file = $this->our_copy->mungeFilename(__FILE__); - // note that the description is silently dropped - HTMLPurifier_ConfigDef::define('Core', 'Name', 'default value', - 'This is a description of the directive.'); - $this->assertIdentical( array( - 'Core' => array( - 'Name' => 'default value' - ) - ), $this->our_copy->info); + // define a namespace + $description = 'Configuration that is always available.'; + HTMLPurifier_ConfigDef::defineNamespace( + 'Core', $description + ); $line = __LINE__; + $this->assertIdentical($this->our_copy->defaults, array( + 'Core' => array() + )); + $this->assertIdentical($this->our_copy->info, array( + 'Core' => array() + )); + $namespace = new HTMLPurifier_ConfigEntity_Namespace(); + $namespace->addDescription($file, $line, $description); + $this->assertIdentical($this->our_copy->info_namespace, array( + 'Core' => $namespace + )); - // test an invalid namespace - HTMLPurifier_ConfigDef::define('Extension', 'Name', false, 'This is '. - 'for an extension, but we have not defined its namespace!'); + + + // define a directive + $description = 'This is a description of the directive.'; + HTMLPurifier_ConfigDef::define( + 'Core', 'Name', 'default value', 'string', + $description + ); $line = __LINE__; + $this->assertIdentical($this->our_copy->defaults, array( + 'Core' => array( + 'Name' => 'default value' + ) + )); + $directive = new HTMLPurifier_ConfigEntity_Directive(); + $directive->type = 'string'; + $directive->addDescription($file, $line, $description); + $this->assertIdentical($this->our_copy->info, array( + 'Core' => array( + 'Name' => $directive + ) + )); + + + + // define a directive in an undefined namespace + HTMLPurifier_ConfigDef::define( + 'Extension', 'Name', false, 'bool', + 'This is for an extension, but we have not defined its namespace!' + ); $this->assertError('Cannot define directive for undefined namespace'); $this->assertNoErrors(); $this->swallowErrors(); - // test overloading already defined value - // ACTUALLY, we probably should allow this behavior, which simply - // means that two class files need that directive. Using debug_backtrace - // we could probably figure which files those are too! :-D - HTMLPurifier_ConfigDef::define('Core', 'Name', 89, - 'What, you\'re not allowed to overload directives? Bummer!'); - $this->assertError('Cannot redefine directive'); + + + // redefine a value in a valid manner + $description = 'Alternative configuration definition'; + HTMLPurifier_ConfigDef::define( + 'Core', 'Name', 'default value', 'string', + $description + ); $line = __LINE__; + $this->assertNoErrors(); + $directive->addDescription($file, $line, $description); + $this->assertIdentical($this->our_copy->info, array( + 'Core' => array( + 'Name' => $directive + ) + )); + + + + // redefine a directive in an invalid manner + HTMLPurifier_ConfigDef::define( + 'Core', 'Name', 'different default', 'string', + 'Inconsistent default or type, cannot redefine' + ); + $this->assertError('Inconsistent default or type, cannot redefine'); $this->assertNoErrors(); $this->swallowErrors(); + + + // make an enumeration + HTMLPurifier_ConfigDef::defineAllowedValues( + 'Core', 'Name', array( + 'Real Value', + 'Real Value 2' + ) + ); + $directive->allowed = array( + 'Real Value' => true, + 'Real Value 2' => true + ); + $this->assertIdentical($this->our_copy->info, array( + 'Core' => array( + 'Name' => $directive + ) + )); + + + + // redefinition of enumeration is cumulative + HTMLPurifier_ConfigDef::defineAllowedValues( + 'Core', 'Name', array( + 'Real Value 3', + ) + ); + $directive->allowed['Real Value 3'] = true; + $this->assertIdentical($this->our_copy->info, array( + 'Core' => array( + 'Name' => $directive + ) + )); + + + + // cannot define enumeration for undefined directive + HTMLPurifier_ConfigDef::defineAllowedValues( + 'Core', 'Foobar', array( + 'Real Value 9', + ) + ); + $this->assertError('Cannot define allowed values for undefined directive'); + $this->assertNoErrors(); + $this->swallowErrors(); + + + + // test defining value aliases for an enumerated value + HTMLPurifier_ConfigDef::defineValueAliases( + 'Core', 'Name', array( + 'Aliased Value' => 'Real Value' + ) + ); + $directive->aliases['Aliased Value'] = 'Real Value'; + $this->assertIdentical($this->our_copy->info, array( + 'Core' => array( + 'Name' => $directive + ) + )); + + + + // redefine should be cumulative + HTMLPurifier_ConfigDef::defineValueAliases( + 'Core', 'Name', array( + 'Aliased Value 2' => 'Real Value 2' + ) + ); + $directive->aliases['Aliased Value 2'] = 'Real Value 2'; + $this->assertIdentical($this->our_copy->info, array( + 'Core' => array( + 'Name' => $directive + ) + )); + + + + // cannot create alias to not-allowed value + HTMLPurifier_ConfigDef::defineValueAliases( + 'Core', 'Name', array( + 'Aliased Value 3' => 'Invalid Value' + ) + ); + $this->assertError('Cannot define alias to value that is not allowed'); + $this->assertNoErrors(); + $this->swallowErrors(); + + + + // cannot create alias for already allowed value + HTMLPurifier_ConfigDef::defineValueAliases( + 'Core', 'Name', array( + 'Real Value' => 'Real Value 2' + ) + ); + $this->assertError('Cannot define alias over allowed value'); + $this->assertNoErrors(); + $this->swallowErrors(); + + + + // define a directive with an invalid type + HTMLPurifier_ConfigDef::define( + 'Core', 'Foobar', false, 'omen', + 'Omen is not a valid type, so we reject this.' + ); + + $this->assertError('Invalid type for configuration directive'); + $this->assertNoErrors(); + $this->swallowErrors(); + + + + // define a directive with inconsistent type + HTMLPurifier_ConfigDef::define( + 'Core', 'Foobaz', 10, 'string', + 'If we say string, we should mean it, not integer 10.' + ); + + $this->assertError('Default value does not match directive type'); + $this->assertNoErrors(); + $this->swallowErrors(); + + + + } + + function assertValid($var, $type, $ret = null) { + $ret = ($ret === null) ? $var : $ret; + $this->assertIdentical($this->our_copy->validate($var, $type), $ret); + } + + function assertInvalid($var, $type) { + $this->assertIdentical($this->our_copy->validate($var, $type), null); + } + + function testValidate() { + + $this->assertValid('foobar', 'string'); + $this->assertValid('FOOBAR', 'istring', '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->assertInvalid(34, 'bool'); + $this->assertValid(array('1', '2', '3'), 'list'); + $this->assertValid(array('1' => true, '2' => true), 'lookup'); + $this->assertValid(array('1', '2'), 'lookup', array('1' => true, '2' => true)); + $this->assertValid(array('foo' => 'bar'), 'hash'); + $this->assertInvalid(array(0 => 'moo'), 'hash'); + $this->assertValid(array(1 => 'moo'), 'hash'); + $this->assertValid(23, 'mixed'); + + } + + function assertMungeFilename($oldname, $newname) { + $this->assertIdentical( + $this->our_copy->mungeFilename($oldname), + $newname + ); + } + + function testMungeFilename() { + + $this->assertMungeFilename( + 'C:\\php\\libs\\htmlpurifier\\library\\HTMLPurifier\\AttrDef.php', + 'HTMLPurifier/AttrDef.php' + ); + + $this->assertMungeFilename( + 'C:\\php\\libs\\htmlpurifier\\library\\HTMLPurifier.php', + 'HTMLPurifier.php' + ); + } } diff --git a/tests/HTMLPurifier/ConfigTest.php b/tests/HTMLPurifier/ConfigTest.php index 45991a55..7421a0c9 100644 --- a/tests/HTMLPurifier/ConfigTest.php +++ b/tests/HTMLPurifier/ConfigTest.php @@ -5,25 +5,51 @@ require_once 'HTMLPurifier/Config.php'; class HTMLPurifier_ConfigTest extends UnitTestCase { + var $our_copy, $old_copy; + + function setUp() { + $our_copy = new HTMLPurifier_ConfigDef(); + $this->old_copy = HTMLPurifier_ConfigDef::instance(); + $this->our_copy =& HTMLPurifier_ConfigDef::instance($our_copy); + } + + function tearDown() { + HTMLPurifier_ConfigDef::instance($this->old_copy); + } + function test() { - $def = new HTMLPurifier_ConfigDef(); - $def->info = array( - 'Core' => array('Key' => false), - 'Attr' => array('Key' => 42), - 'Extension' => array('Pert' => 'moo') + HTMLPurifier_ConfigDef::defineNamespace('Core', 'Corestuff'); + HTMLPurifier_ConfigDef::defineNamespace('Attr', 'Attributes'); + HTMLPurifier_ConfigDef::defineNamespace('Extension', 'Extensible'); + + HTMLPurifier_ConfigDef::define( + 'Core', 'Key', false, 'bool', 'A boolean directive.' + ); + HTMLPurifier_ConfigDef::define( + 'Attr', 'Key', 42, 'int', 'An integer directive.' + ); + HTMLPurifier_ConfigDef::define( + 'Extension', 'Pert', 'foo', 'string', 'A string directive.' ); - $config = new HTMLPurifier_Config($def); + HTMLPurifier_ConfigDef::defineAllowedValues( + 'Extension', 'Pert', array('foo', 'moo') + ); + HTMLPurifier_ConfigDef::defineValueAliases( + 'Extension', 'Pert', array('cow' => 'moo') + ); + + $config = HTMLPurifier_Config::createDefault(); // test default value retrieval $this->assertIdentical($config->get('Core', 'Key'), false); $this->assertIdentical($config->get('Attr', 'Key'), 42); - $this->assertIdentical($config->get('Extension', 'Pert'), 'moo'); + $this->assertIdentical($config->get('Extension', 'Pert'), 'foo'); // set some values - $config->set('Core', 'Key', 'foobar'); - $this->assertIdentical($config->get('Core', 'Key'), 'foobar'); + $config->set('Core', 'Key', true); + $this->assertIdentical($config->get('Core', 'Key'), true); // try to retrieve undefined value $config->get('Core', 'NotDefined'); @@ -37,6 +63,23 @@ class HTMLPurifier_ConfigTest extends UnitTestCase $this->assertNoErrors(); $this->swallowErrors(); + // try to set not allowed value + $config->set('Extension', 'Pert', 'wizard'); + $this->assertError('Value not supported'); + $this->assertNoErrors(); + $this->swallowErrors(); + + // try to set not allowed value + $config->set('Extension', 'Pert', 34); + $this->assertError('Value is of invalid type'); + $this->assertNoErrors(); + $this->swallowErrors(); + + // set aliased value + $config->set('Extension', 'Pert', 'cow'); + $this->assertNoErrors(); + $this->assertIdentical($config->get('Extension', 'Pert'), 'moo'); + } }