htmlpurifier update - compatibility issue with language library autoloader

This commit is contained in:
friendica 2015-01-01 22:18:27 -08:00
parent 545e47933a
commit a0052f0176
262 changed files with 13415 additions and 6016 deletions

View File

@ -3,6 +3,7 @@
/** /**
* @file * @file
* Convenience file that registers autoload handler for HTML Purifier. * Convenience file that registers autoload handler for HTML Purifier.
* It also does some sanity checks.
*/ */
if (function_exists('spl_autoload_register') && function_exists('spl_autoload_unregister')) { if (function_exists('spl_autoload_register') && function_exists('spl_autoload_unregister')) {
@ -13,9 +14,14 @@ if (function_exists('spl_autoload_register') && function_exists('spl_autoload_un
spl_autoload_register('__autoload'); spl_autoload_register('__autoload');
} }
} elseif (!function_exists('__autoload')) { } elseif (!function_exists('__autoload')) {
function __autoload($class) { function __autoload($class)
{
return HTMLPurifier_Bootstrap::autoload($class); return HTMLPurifier_Bootstrap::autoload($class);
} }
} }
if (ini_get('zend.ze1_compatibility_mode')) {
trigger_error("HTML Purifier is not compatible with zend.ze1_compatibility_mode; please turn it off", E_USER_ERROR);
}
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -0,0 +1,4 @@
<?php
if (!defined('HTMLPURIFIER_PREFIX')) {
define('HTMLPURIFIER_PREFIX', __DIR__);
}

View File

@ -8,11 +8,13 @@
/** /**
* Purify HTML. * Purify HTML.
* @param $html String HTML to purify * @param string $html String HTML to purify
* @param $config Configuration to use, can be any value accepted by * @param mixed $config Configuration to use, can be any value accepted by
* HTMLPurifier_Config::create() * HTMLPurifier_Config::create()
* @return string
*/ */
function HTMLPurifier($html, $config = null) { function HTMLPurifier($html, $config = null)
{
static $purifier = false; static $purifier = false;
if (!$purifier) { if (!$purifier) {
$purifier = new HTMLPurifier(); $purifier = new HTMLPurifier();

View File

@ -7,7 +7,7 @@
* primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
* FILE, changes will be overwritten the next time the script is run. * FILE, changes will be overwritten the next time the script is run.
* *
* @version 4.1.1 * @version 4.6.0
* *
* @warning * @warning
* You must *not* include any other HTML Purifier files before this file, * You must *not* include any other HTML Purifier files before this file,
@ -19,6 +19,7 @@
*/ */
require 'HTMLPurifier.php'; require 'HTMLPurifier.php';
require 'HTMLPurifier/Arborize.php';
require 'HTMLPurifier/AttrCollections.php'; require 'HTMLPurifier/AttrCollections.php';
require 'HTMLPurifier/AttrDef.php'; require 'HTMLPurifier/AttrDef.php';
require 'HTMLPurifier/AttrTransform.php'; require 'HTMLPurifier/AttrTransform.php';
@ -54,9 +55,11 @@ require 'HTMLPurifier/Language.php';
require 'HTMLPurifier/LanguageFactory.php'; require 'HTMLPurifier/LanguageFactory.php';
require 'HTMLPurifier/Length.php'; require 'HTMLPurifier/Length.php';
require 'HTMLPurifier/Lexer.php'; require 'HTMLPurifier/Lexer.php';
require 'HTMLPurifier/Node.php';
require 'HTMLPurifier/PercentEncoder.php'; require 'HTMLPurifier/PercentEncoder.php';
require 'HTMLPurifier/PropertyList.php'; require 'HTMLPurifier/PropertyList.php';
require 'HTMLPurifier/PropertyListIterator.php'; require 'HTMLPurifier/PropertyListIterator.php';
require 'HTMLPurifier/Queue.php';
require 'HTMLPurifier/Strategy.php'; require 'HTMLPurifier/Strategy.php';
require 'HTMLPurifier/StringHash.php'; require 'HTMLPurifier/StringHash.php';
require 'HTMLPurifier/StringHashParser.php'; require 'HTMLPurifier/StringHashParser.php';
@ -72,7 +75,9 @@ require 'HTMLPurifier/URISchemeRegistry.php';
require 'HTMLPurifier/UnitConverter.php'; require 'HTMLPurifier/UnitConverter.php';
require 'HTMLPurifier/VarParser.php'; require 'HTMLPurifier/VarParser.php';
require 'HTMLPurifier/VarParserException.php'; require 'HTMLPurifier/VarParserException.php';
require 'HTMLPurifier/Zipper.php';
require 'HTMLPurifier/AttrDef/CSS.php'; require 'HTMLPurifier/AttrDef/CSS.php';
require 'HTMLPurifier/AttrDef/Clone.php';
require 'HTMLPurifier/AttrDef/Enum.php'; require 'HTMLPurifier/AttrDef/Enum.php';
require 'HTMLPurifier/AttrDef/Integer.php'; require 'HTMLPurifier/AttrDef/Integer.php';
require 'HTMLPurifier/AttrDef/Lang.php'; require 'HTMLPurifier/AttrDef/Lang.php';
@ -90,6 +95,7 @@ require 'HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
require 'HTMLPurifier/AttrDef/CSS/Filter.php'; require 'HTMLPurifier/AttrDef/CSS/Filter.php';
require 'HTMLPurifier/AttrDef/CSS/Font.php'; require 'HTMLPurifier/AttrDef/CSS/Font.php';
require 'HTMLPurifier/AttrDef/CSS/FontFamily.php'; require 'HTMLPurifier/AttrDef/CSS/FontFamily.php';
require 'HTMLPurifier/AttrDef/CSS/Ident.php';
require 'HTMLPurifier/AttrDef/CSS/ImportantDecorator.php'; require 'HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
require 'HTMLPurifier/AttrDef/CSS/Length.php'; require 'HTMLPurifier/AttrDef/CSS/Length.php';
require 'HTMLPurifier/AttrDef/CSS/ListStyle.php'; require 'HTMLPurifier/AttrDef/CSS/ListStyle.php';
@ -125,14 +131,17 @@ require 'HTMLPurifier/AttrTransform/Lang.php';
require 'HTMLPurifier/AttrTransform/Length.php'; require 'HTMLPurifier/AttrTransform/Length.php';
require 'HTMLPurifier/AttrTransform/Name.php'; require 'HTMLPurifier/AttrTransform/Name.php';
require 'HTMLPurifier/AttrTransform/NameSync.php'; require 'HTMLPurifier/AttrTransform/NameSync.php';
require 'HTMLPurifier/AttrTransform/Nofollow.php';
require 'HTMLPurifier/AttrTransform/SafeEmbed.php'; require 'HTMLPurifier/AttrTransform/SafeEmbed.php';
require 'HTMLPurifier/AttrTransform/SafeObject.php'; require 'HTMLPurifier/AttrTransform/SafeObject.php';
require 'HTMLPurifier/AttrTransform/SafeParam.php'; require 'HTMLPurifier/AttrTransform/SafeParam.php';
require 'HTMLPurifier/AttrTransform/ScriptRequired.php'; require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
require 'HTMLPurifier/AttrTransform/TargetBlank.php';
require 'HTMLPurifier/AttrTransform/Textarea.php'; require 'HTMLPurifier/AttrTransform/Textarea.php';
require 'HTMLPurifier/ChildDef/Chameleon.php'; require 'HTMLPurifier/ChildDef/Chameleon.php';
require 'HTMLPurifier/ChildDef/Custom.php'; require 'HTMLPurifier/ChildDef/Custom.php';
require 'HTMLPurifier/ChildDef/Empty.php'; require 'HTMLPurifier/ChildDef/Empty.php';
require 'HTMLPurifier/ChildDef/List.php';
require 'HTMLPurifier/ChildDef/Required.php'; require 'HTMLPurifier/ChildDef/Required.php';
require 'HTMLPurifier/ChildDef/Optional.php'; require 'HTMLPurifier/ChildDef/Optional.php';
require 'HTMLPurifier/ChildDef/StrictBlockquote.php'; require 'HTMLPurifier/ChildDef/StrictBlockquote.php';
@ -147,10 +156,12 @@ require 'HTMLPurifier/HTMLModule/CommonAttributes.php';
require 'HTMLPurifier/HTMLModule/Edit.php'; require 'HTMLPurifier/HTMLModule/Edit.php';
require 'HTMLPurifier/HTMLModule/Forms.php'; require 'HTMLPurifier/HTMLModule/Forms.php';
require 'HTMLPurifier/HTMLModule/Hypertext.php'; require 'HTMLPurifier/HTMLModule/Hypertext.php';
require 'HTMLPurifier/HTMLModule/Iframe.php';
require 'HTMLPurifier/HTMLModule/Image.php'; require 'HTMLPurifier/HTMLModule/Image.php';
require 'HTMLPurifier/HTMLModule/Legacy.php'; require 'HTMLPurifier/HTMLModule/Legacy.php';
require 'HTMLPurifier/HTMLModule/List.php'; require 'HTMLPurifier/HTMLModule/List.php';
require 'HTMLPurifier/HTMLModule/Name.php'; require 'HTMLPurifier/HTMLModule/Name.php';
require 'HTMLPurifier/HTMLModule/Nofollow.php';
require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php'; require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
require 'HTMLPurifier/HTMLModule/Object.php'; require 'HTMLPurifier/HTMLModule/Object.php';
require 'HTMLPurifier/HTMLModule/Presentation.php'; require 'HTMLPurifier/HTMLModule/Presentation.php';
@ -158,10 +169,12 @@ require 'HTMLPurifier/HTMLModule/Proprietary.php';
require 'HTMLPurifier/HTMLModule/Ruby.php'; require 'HTMLPurifier/HTMLModule/Ruby.php';
require 'HTMLPurifier/HTMLModule/SafeEmbed.php'; require 'HTMLPurifier/HTMLModule/SafeEmbed.php';
require 'HTMLPurifier/HTMLModule/SafeObject.php'; require 'HTMLPurifier/HTMLModule/SafeObject.php';
require 'HTMLPurifier/HTMLModule/SafeScripting.php';
require 'HTMLPurifier/HTMLModule/Scripting.php'; require 'HTMLPurifier/HTMLModule/Scripting.php';
require 'HTMLPurifier/HTMLModule/StyleAttribute.php'; require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
require 'HTMLPurifier/HTMLModule/Tables.php'; require 'HTMLPurifier/HTMLModule/Tables.php';
require 'HTMLPurifier/HTMLModule/Target.php'; require 'HTMLPurifier/HTMLModule/Target.php';
require 'HTMLPurifier/HTMLModule/TargetBlank.php';
require 'HTMLPurifier/HTMLModule/Text.php'; require 'HTMLPurifier/HTMLModule/Text.php';
require 'HTMLPurifier/HTMLModule/Tidy.php'; require 'HTMLPurifier/HTMLModule/Tidy.php';
require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php'; require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
@ -180,6 +193,9 @@ require 'HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php';
require 'HTMLPurifier/Injector/SafeObject.php'; require 'HTMLPurifier/Injector/SafeObject.php';
require 'HTMLPurifier/Lexer/DOMLex.php'; require 'HTMLPurifier/Lexer/DOMLex.php';
require 'HTMLPurifier/Lexer/DirectLex.php'; require 'HTMLPurifier/Lexer/DirectLex.php';
require 'HTMLPurifier/Node/Comment.php';
require 'HTMLPurifier/Node/Element.php';
require 'HTMLPurifier/Node/Text.php';
require 'HTMLPurifier/Strategy/Composite.php'; require 'HTMLPurifier/Strategy/Composite.php';
require 'HTMLPurifier/Strategy/Core.php'; require 'HTMLPurifier/Strategy/Core.php';
require 'HTMLPurifier/Strategy/FixNesting.php'; require 'HTMLPurifier/Strategy/FixNesting.php';
@ -196,10 +212,13 @@ require 'HTMLPurifier/Token/Start.php';
require 'HTMLPurifier/Token/Text.php'; require 'HTMLPurifier/Token/Text.php';
require 'HTMLPurifier/URIFilter/DisableExternal.php'; require 'HTMLPurifier/URIFilter/DisableExternal.php';
require 'HTMLPurifier/URIFilter/DisableExternalResources.php'; require 'HTMLPurifier/URIFilter/DisableExternalResources.php';
require 'HTMLPurifier/URIFilter/DisableResources.php';
require 'HTMLPurifier/URIFilter/HostBlacklist.php'; require 'HTMLPurifier/URIFilter/HostBlacklist.php';
require 'HTMLPurifier/URIFilter/MakeAbsolute.php'; require 'HTMLPurifier/URIFilter/MakeAbsolute.php';
require 'HTMLPurifier/URIFilter/Munge.php'; require 'HTMLPurifier/URIFilter/Munge.php';
require 'HTMLPurifier/URIFilter/SafeIframe.php';
require 'HTMLPurifier/URIScheme/data.php'; require 'HTMLPurifier/URIScheme/data.php';
require 'HTMLPurifier/URIScheme/file.php';
require 'HTMLPurifier/URIScheme/ftp.php'; require 'HTMLPurifier/URIScheme/ftp.php';
require 'HTMLPurifier/URIScheme/http.php'; require 'HTMLPurifier/URIScheme/http.php';
require 'HTMLPurifier/URIScheme/https.php'; require 'HTMLPurifier/URIScheme/https.php';

View File

@ -7,7 +7,8 @@
require_once dirname(__FILE__) . '/HTMLPurifier.auto.php'; require_once dirname(__FILE__) . '/HTMLPurifier.auto.php';
function kses($string, $allowed_html, $allowed_protocols = null) { function kses($string, $allowed_html, $allowed_protocols = null)
{
$config = HTMLPurifier_Config::createDefault(); $config = HTMLPurifier_Config::createDefault();
$allowed_elements = array(); $allowed_elements = array();
$allowed_attributes = array(); $allowed_attributes = array();
@ -19,7 +20,6 @@ function kses($string, $allowed_html, $allowed_protocols = null) {
} }
$config->set('HTML.AllowedElements', $allowed_elements); $config->set('HTML.AllowedElements', $allowed_elements);
$config->set('HTML.AllowedAttributes', $allowed_attributes); $config->set('HTML.AllowedAttributes', $allowed_attributes);
$allowed_schemes = array();
if ($allowed_protocols !== null) { if ($allowed_protocols !== null) {
$config->set('URI.AllowedSchemes', $allowed_protocols); $config->set('URI.AllowedSchemes', $allowed_protocols);
} }

View File

@ -19,7 +19,7 @@
*/ */
/* /*
HTML Purifier 4.1.1 - Standards Compliant HTML Filtering HTML Purifier 4.6.0 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
@ -54,66 +54,97 @@
class HTMLPurifier class HTMLPurifier
{ {
/** Version of HTML Purifier */ /**
public $version = '4.1.1'; * Version of HTML Purifier.
* @type string
/** Constant with version of HTML Purifier */ */
const VERSION = '4.1.1'; public $version = '4.6.0';
/** Global configuration object */
public $config;
/** Array of extra HTMLPurifier_Filter objects to run on HTML, for backwards compatibility */
private $filters = array();
/** Single instance of HTML Purifier */
private static $instance;
protected $strategy, $generator;
/** /**
* Resultant HTMLPurifier_Context of last run purification. Is an array * Constant with version of HTML Purifier.
* of contexts if the last called method was purifyArray(). */
const VERSION = '4.6.0';
/**
* Global configuration object.
* @type HTMLPurifier_Config
*/
public $config;
/**
* Array of extra filter objects to run on HTML,
* for backwards compatibility.
* @type HTMLPurifier_Filter[]
*/
private $filters = array();
/**
* Single instance of HTML Purifier.
* @type HTMLPurifier
*/
private static $instance;
/**
* @type HTMLPurifier_Strategy_Core
*/
protected $strategy;
/**
* @type HTMLPurifier_Generator
*/
protected $generator;
/**
* Resultant context of last run purification.
* Is an array of contexts if the last called method was purifyArray().
* @type HTMLPurifier_Context
*/ */
public $context; public $context;
/** /**
* Initializes the purifier. * Initializes the purifier.
* @param $config Optional HTMLPurifier_Config object for all instances of *
* the purifier, if omitted, a default configuration is * @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object
* supplied (which can be overridden on a per-use basis). * for all instances of the purifier, if omitted, a default
* configuration is supplied (which can be overridden on a
* per-use basis).
* The parameter can also be any type that * The parameter can also be any type that
* HTMLPurifier_Config::create() supports. * HTMLPurifier_Config::create() supports.
*/ */
public function __construct($config = null) { public function __construct($config = null)
{
$this->config = HTMLPurifier_Config::create($config); $this->config = HTMLPurifier_Config::create($config);
$this->strategy = new HTMLPurifier_Strategy_Core(); $this->strategy = new HTMLPurifier_Strategy_Core();
} }
/** /**
* Adds a filter to process the output. First come first serve * Adds a filter to process the output. First come first serve
* @param $filter HTMLPurifier_Filter object *
* @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object
*/ */
public function addFilter($filter) { public function addFilter($filter)
trigger_error('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom', E_USER_WARNING); {
trigger_error(
'HTMLPurifier->addFilter() is deprecated, use configuration directives' .
' in the Filter namespace or Filter.Custom',
E_USER_WARNING
);
$this->filters[] = $filter; $this->filters[] = $filter;
} }
/** /**
* Filters an HTML snippet/document to be XSS-free and standards-compliant. * Filters an HTML snippet/document to be XSS-free and standards-compliant.
* *
* @param $html String of HTML to purify * @param string $html String of HTML to purify
* @param $config HTMLPurifier_Config object for this operation, if omitted, * @param HTMLPurifier_Config $config Config object for this operation,
* defaults to the config object specified during this * if omitted, defaults to the config object specified during this
* object's construction. The parameter can also be any type * object's construction. The parameter can also be any type
* that HTMLPurifier_Config::create() supports. * that HTMLPurifier_Config::create() supports.
* @return Purified HTML *
* @return string Purified HTML
*/ */
public function purify($html, $config = null) { public function purify($html, $config = null)
{
// :TODO: make the config merge in, instead of replace // :TODO: make the config merge in, instead of replace
$config = $config ? HTMLPurifier_Config::create($config) : $this->config; $config = $config ? HTMLPurifier_Config::create($config) : $this->config;
@ -151,8 +182,12 @@ class HTMLPurifier
unset($filter_flags['Custom']); unset($filter_flags['Custom']);
$filters = array(); $filters = array();
foreach ($filter_flags as $filter => $flag) { foreach ($filter_flags as $filter => $flag) {
if (!$flag) continue; if (!$flag) {
if (strpos($filter, '.') !== false) continue; continue;
}
if (strpos($filter, '.') !== false) {
continue;
}
$class = "HTMLPurifier_Filter_$filter"; $class = "HTMLPurifier_Filter_$filter";
$filters[] = new $class; $filters[] = new $class;
} }
@ -175,9 +210,12 @@ class HTMLPurifier
// list of un-purified tokens // list of un-purified tokens
$lexer->tokenizeHTML( $lexer->tokenizeHTML(
// un-purified HTML // un-purified HTML
$html, $config, $context $html,
$config,
$context
), ),
$config, $context $config,
$context
) )
); );
@ -192,11 +230,15 @@ class HTMLPurifier
/** /**
* Filters an array of HTML snippets * Filters an array of HTML snippets
* @param $config Optional HTMLPurifier_Config object for this operation. *
* @param string[] $array_of_html Array of html snippets
* @param HTMLPurifier_Config $config Optional config object for this operation.
* See HTMLPurifier::purify() for more details. * See HTMLPurifier::purify() for more details.
* @return Array of purified HTML *
* @return string[] Array of purified HTML
*/ */
public function purifyArray($array_of_html, $config = null) { public function purifyArray($array_of_html, $config = null)
{
$context_array = array(); $context_array = array();
foreach ($array_of_html as $key => $html) { foreach ($array_of_html as $key => $html) {
$array_of_html[$key] = $this->purify($html, $config); $array_of_html[$key] = $this->purify($html, $config);
@ -208,11 +250,16 @@ class HTMLPurifier
/** /**
* Singleton for enforcing just one HTML Purifier in your system * Singleton for enforcing just one HTML Purifier in your system
* @param $prototype Optional prototype HTMLPurifier instance to *
* overload singleton with, or HTMLPurifier_Config * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
* instance to configure the generated version with. * HTMLPurifier instance to overload singleton with,
* or HTMLPurifier_Config instance to configure the
* generated version with.
*
* @return HTMLPurifier
*/ */
public static function instance($prototype = null) { public static function instance($prototype = null)
{
if (!self::$instance || $prototype) { if (!self::$instance || $prototype) {
if ($prototype instanceof HTMLPurifier) { if ($prototype instanceof HTMLPurifier) {
self::$instance = $prototype; self::$instance = $prototype;
@ -226,12 +273,20 @@ class HTMLPurifier
} }
/** /**
* Singleton for enforcing just one HTML Purifier in your system
*
* @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
* HTMLPurifier instance to overload singleton with,
* or HTMLPurifier_Config instance to configure the
* generated version with.
*
* @return HTMLPurifier
* @note Backwards compatibility, see instance() * @note Backwards compatibility, see instance()
*/ */
public static function getInstance($prototype = null) { public static function getInstance($prototype = null)
{
return HTMLPurifier::instance($prototype); return HTMLPurifier::instance($prototype);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -13,6 +13,7 @@
$__dir = dirname(__FILE__); $__dir = dirname(__FILE__);
require_once $__dir . '/HTMLPurifier.php'; require_once $__dir . '/HTMLPurifier.php';
require_once $__dir . '/HTMLPurifier/Arborize.php';
require_once $__dir . '/HTMLPurifier/AttrCollections.php'; require_once $__dir . '/HTMLPurifier/AttrCollections.php';
require_once $__dir . '/HTMLPurifier/AttrDef.php'; require_once $__dir . '/HTMLPurifier/AttrDef.php';
require_once $__dir . '/HTMLPurifier/AttrTransform.php'; require_once $__dir . '/HTMLPurifier/AttrTransform.php';
@ -48,9 +49,11 @@ require_once $__dir . '/HTMLPurifier/Language.php';
require_once $__dir . '/HTMLPurifier/LanguageFactory.php'; require_once $__dir . '/HTMLPurifier/LanguageFactory.php';
require_once $__dir . '/HTMLPurifier/Length.php'; require_once $__dir . '/HTMLPurifier/Length.php';
require_once $__dir . '/HTMLPurifier/Lexer.php'; require_once $__dir . '/HTMLPurifier/Lexer.php';
require_once $__dir . '/HTMLPurifier/Node.php';
require_once $__dir . '/HTMLPurifier/PercentEncoder.php'; require_once $__dir . '/HTMLPurifier/PercentEncoder.php';
require_once $__dir . '/HTMLPurifier/PropertyList.php'; require_once $__dir . '/HTMLPurifier/PropertyList.php';
require_once $__dir . '/HTMLPurifier/PropertyListIterator.php'; require_once $__dir . '/HTMLPurifier/PropertyListIterator.php';
require_once $__dir . '/HTMLPurifier/Queue.php';
require_once $__dir . '/HTMLPurifier/Strategy.php'; require_once $__dir . '/HTMLPurifier/Strategy.php';
require_once $__dir . '/HTMLPurifier/StringHash.php'; require_once $__dir . '/HTMLPurifier/StringHash.php';
require_once $__dir . '/HTMLPurifier/StringHashParser.php'; require_once $__dir . '/HTMLPurifier/StringHashParser.php';
@ -66,7 +69,9 @@ require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php';
require_once $__dir . '/HTMLPurifier/UnitConverter.php'; require_once $__dir . '/HTMLPurifier/UnitConverter.php';
require_once $__dir . '/HTMLPurifier/VarParser.php'; require_once $__dir . '/HTMLPurifier/VarParser.php';
require_once $__dir . '/HTMLPurifier/VarParserException.php'; require_once $__dir . '/HTMLPurifier/VarParserException.php';
require_once $__dir . '/HTMLPurifier/Zipper.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Clone.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php'; require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php'; require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php'; require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php';
@ -84,6 +89,7 @@ require_once $__dir . '/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Filter.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Filter.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Font.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Font.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/FontFamily.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/FontFamily.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Ident.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Length.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Length.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ListStyle.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ListStyle.php';
@ -119,14 +125,17 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Nofollow.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Empty.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Empty.php';
require_once $__dir . '/HTMLPurifier/ChildDef/List.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Required.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Required.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Optional.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Optional.php';
require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php'; require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php';
@ -141,10 +150,12 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/CommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Hypertext.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Hypertext.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Iframe.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Image.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Image.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/List.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/List.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Nofollow.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php';
@ -152,10 +163,12 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeScripting.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
@ -174,6 +187,9 @@ require_once $__dir . '/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php';
require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php'; require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php';
require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php'; require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php';
require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php'; require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php';
require_once $__dir . '/HTMLPurifier/Node/Comment.php';
require_once $__dir . '/HTMLPurifier/Node/Element.php';
require_once $__dir . '/HTMLPurifier/Node/Text.php';
require_once $__dir . '/HTMLPurifier/Strategy/Composite.php'; require_once $__dir . '/HTMLPurifier/Strategy/Composite.php';
require_once $__dir . '/HTMLPurifier/Strategy/Core.php'; require_once $__dir . '/HTMLPurifier/Strategy/Core.php';
require_once $__dir . '/HTMLPurifier/Strategy/FixNesting.php'; require_once $__dir . '/HTMLPurifier/Strategy/FixNesting.php';
@ -190,10 +206,13 @@ require_once $__dir . '/HTMLPurifier/Token/Start.php';
require_once $__dir . '/HTMLPurifier/Token/Text.php'; require_once $__dir . '/HTMLPurifier/Token/Text.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php'; require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php'; require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableResources.php';
require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php'; require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php';
require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php'; require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php';
require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php'; require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php';
require_once $__dir . '/HTMLPurifier/URIFilter/SafeIframe.php';
require_once $__dir . '/HTMLPurifier/URIScheme/data.php'; require_once $__dir . '/HTMLPurifier/URIScheme/data.php';
require_once $__dir . '/HTMLPurifier/URIScheme/file.php';
require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php'; require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php';
require_once $__dir . '/HTMLPurifier/URIScheme/http.php'; require_once $__dir . '/HTMLPurifier/URIScheme/http.php';
require_once $__dir . '/HTMLPurifier/URIScheme/https.php'; require_once $__dir . '/HTMLPurifier/URIScheme/https.php';

View File

@ -0,0 +1,71 @@
<?php
/**
* Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node,
* and back again.
*
* @note This transformation is not an equivalence. We mutate the input
* token stream to make it so; see all [MUT] markers in code.
*/
class HTMLPurifier_Arborize
{
public static function arborize($tokens, $config, $context) {
$definition = $config->getHTMLDefinition();
$parent = new HTMLPurifier_Token_Start($definition->info_parent);
$stack = array($parent->toNode());
foreach ($tokens as $token) {
$token->skip = null; // [MUT]
$token->carryover = null; // [MUT]
if ($token instanceof HTMLPurifier_Token_End) {
$token->start = null; // [MUT]
$r = array_pop($stack);
assert($r->name === $token->name);
assert(empty($token->attr));
$r->endCol = $token->col;
$r->endLine = $token->line;
$r->endArmor = $token->armor;
continue;
}
$node = $token->toNode();
$stack[count($stack)-1]->children[] = $node;
if ($token instanceof HTMLPurifier_Token_Start) {
$stack[] = $node;
}
}
assert(count($stack) == 1);
return $stack[0];
}
public static function flatten($node, $config, $context) {
$level = 0;
$nodes = array($level => new HTMLPurifier_Queue(array($node)));
$closingTokens = array();
$tokens = array();
do {
while (!$nodes[$level]->isEmpty()) {
$node = $nodes[$level]->shift(); // FIFO
list($start, $end) = $node->toTokenPair();
if ($level > 0) {
$tokens[] = $start;
}
if ($end !== NULL) {
$closingTokens[$level][] = $end;
}
if ($node instanceof HTMLPurifier_Node_Element) {
$level++;
$nodes[$level] = new HTMLPurifier_Queue();
foreach ($node->children as $childNode) {
$nodes[$level]->push($childNode);
}
}
}
$level--;
if ($level && isset($closingTokens[$level])) {
while ($token = array_pop($closingTokens[$level])) {
$tokens[] = $token;
}
}
} while ($level > 0);
return $tokens;
}
}

View File

@ -8,7 +8,8 @@ class HTMLPurifier_AttrCollections
{ {
/** /**
* Associative array of attribute collections, indexed by name * Associative array of attribute collections, indexed by name.
* @type array
*/ */
public $info = array(); public $info = array();
@ -16,10 +17,11 @@ class HTMLPurifier_AttrCollections
* Performs all expansions on internal data for use by other inclusions * Performs all expansions on internal data for use by other inclusions
* It also collects all attribute collection extensions from * It also collects all attribute collection extensions from
* modules * modules
* @param $attr_types HTMLPurifier_AttrTypes instance * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
* @param $modules Hash array of HTMLPurifier_HTMLModule members * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
*/ */
public function __construct($attr_types, $modules) { public function __construct($attr_types, $modules)
{
// load extensions from the modules // load extensions from the modules
foreach ($modules as $module) { foreach ($modules as $module) {
foreach ($module->attr_collections as $coll_i => $coll) { foreach ($module->attr_collections as $coll_i => $coll) {
@ -30,7 +32,9 @@ class HTMLPurifier_AttrCollections
if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) { if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
// merge in includes // merge in includes
$this->info[$coll_i][$attr_i] = array_merge( $this->info[$coll_i][$attr_i] = array_merge(
$this->info[$coll_i][$attr_i], $attr); $this->info[$coll_i][$attr_i],
$attr
);
continue; continue;
} }
$this->info[$coll_i][$attr_i] = $attr; $this->info[$coll_i][$attr_i] = $attr;
@ -49,20 +53,29 @@ class HTMLPurifier_AttrCollections
/** /**
* Takes a reference to an attribute associative array and performs * Takes a reference to an attribute associative array and performs
* all inclusions specified by the zero index. * all inclusions specified by the zero index.
* @param &$attr Reference to attribute array * @param array &$attr Reference to attribute array
*/ */
public function performInclusions(&$attr) { public function performInclusions(&$attr)
if (!isset($attr[0])) return; {
if (!isset($attr[0])) {
return;
}
$merge = $attr[0]; $merge = $attr[0];
$seen = array(); // recursion guard $seen = array(); // recursion guard
// loop through all the inclusions // loop through all the inclusions
for ($i = 0; isset($merge[$i]); $i++) { for ($i = 0; isset($merge[$i]); $i++) {
if (isset($seen[$merge[$i]])) continue; if (isset($seen[$merge[$i]])) {
continue;
}
$seen[$merge[$i]] = true; $seen[$merge[$i]] = true;
// foreach attribute of the inclusion, copy it over // foreach attribute of the inclusion, copy it over
if (!isset($this->info[$merge[$i]])) continue; if (!isset($this->info[$merge[$i]])) {
continue;
}
foreach ($this->info[$merge[$i]] as $key => $value) { foreach ($this->info[$merge[$i]] as $key => $value) {
if (isset($attr[$key])) continue; // also catches more inclusions if (isset($attr[$key])) {
continue;
} // also catches more inclusions
$attr[$key] = $value; $attr[$key] = $value;
} }
if (isset($this->info[$merge[$i]][0])) { if (isset($this->info[$merge[$i]][0])) {
@ -76,20 +89,24 @@ class HTMLPurifier_AttrCollections
/** /**
* Expands all string identifiers in an attribute array by replacing * Expands all string identifiers in an attribute array by replacing
* them with the appropriate values inside HTMLPurifier_AttrTypes * them with the appropriate values inside HTMLPurifier_AttrTypes
* @param &$attr Reference to attribute array * @param array &$attr Reference to attribute array
* @param $attr_types HTMLPurifier_AttrTypes instance * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
*/ */
public function expandIdentifiers(&$attr, $attr_types) { public function expandIdentifiers(&$attr, $attr_types)
{
// because foreach will process new elements we add, make sure we // because foreach will process new elements we add, make sure we
// skip duplicates // skip duplicates
$processed = array(); $processed = array();
foreach ($attr as $def_i => $def) { foreach ($attr as $def_i => $def) {
// skip inclusions // skip inclusions
if ($def_i === 0) continue; if ($def_i === 0) {
continue;
}
if (isset($processed[$def_i])) continue; if (isset($processed[$def_i])) {
continue;
}
// determine whether or not attribute is required // determine whether or not attribute is required
if ($required = (strpos($def_i, '*') !== false)) { if ($required = (strpos($def_i, '*') !== false)) {
@ -120,9 +137,7 @@ class HTMLPurifier_AttrCollections
unset($attr[$def_i]); unset($attr[$def_i]);
} }
} }
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -14,23 +14,25 @@ abstract class HTMLPurifier_AttrDef
{ {
/** /**
* Tells us whether or not an HTML attribute is minimized. Has no * Tells us whether or not an HTML attribute is minimized.
* meaning in other contexts. * Has no meaning in other contexts.
* @type bool
*/ */
public $minimized = false; public $minimized = false;
/** /**
* Tells us whether or not an HTML attribute is required. Has no * Tells us whether or not an HTML attribute is required.
* meaning in other contexts * Has no meaning in other contexts
* @type bool
*/ */
public $required = false; public $required = false;
/** /**
* Validates and cleans passed string according to a definition. * Validates and cleans passed string according to a definition.
* *
* @param $string String to be validated and cleaned. * @param string $string String to be validated and cleaned.
* @param $config Mandatory HTMLPurifier_Config object. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
* @param $context Mandatory HTMLPurifier_AttrContext object. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object.
*/ */
abstract public function validate($string, $config, $context); abstract public function validate($string, $config, $context);
@ -55,7 +57,8 @@ abstract class HTMLPurifier_AttrDef
* parsing XML, thus, this behavior may still be correct. We * parsing XML, thus, this behavior may still be correct. We
* assume that newlines have been normalized. * assume that newlines have been normalized.
*/ */
public function parseCDATA($string) { public function parseCDATA($string)
{
$string = trim($string); $string = trim($string);
$string = str_replace(array("\n", "\t", "\r"), ' ', $string); $string = str_replace(array("\n", "\t", "\r"), ' ', $string);
return $string; return $string;
@ -63,10 +66,11 @@ abstract class HTMLPurifier_AttrDef
/** /**
* Factory method for creating this class from a string. * Factory method for creating this class from a string.
* @param $string String construction info * @param string $string String construction info
* @return Created AttrDef object corresponding to $string * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string
*/ */
public function make($string) { public function make($string)
{
// default implementation, return a flyweight of this object. // default implementation, return a flyweight of this object.
// If $string has an effect on the returned object (i.e. you // If $string has an effect on the returned object (i.e. you
// need to overload this method), it is best // need to overload this method), it is best
@ -77,8 +81,11 @@ abstract class HTMLPurifier_AttrDef
/** /**
* Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
* properly. THIS IS A HACK! * properly. THIS IS A HACK!
* @param string $string a CSS colour definition
* @return string
*/ */
protected function mungeRgb($string) { protected function mungeRgb($string)
{
return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string); return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
} }
@ -86,7 +93,8 @@ abstract class HTMLPurifier_AttrDef
* Parses a possibly escaped CSS string and returns the "pure" * Parses a possibly escaped CSS string and returns the "pure"
* version of it. * version of it.
*/ */
protected function expandCSSEscape($string) { protected function expandCSSEscape($string)
{
// flexibly parse it // flexibly parse it
$ret = ''; $ret = '';
for ($i = 0, $c = strlen($string); $i < $c; $i++) { for ($i = 0, $c = strlen($string); $i < $c; $i++) {
@ -99,25 +107,32 @@ abstract class HTMLPurifier_AttrDef
if (ctype_xdigit($string[$i])) { if (ctype_xdigit($string[$i])) {
$code = $string[$i]; $code = $string[$i];
for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
if (!ctype_xdigit($string[$i])) break; if (!ctype_xdigit($string[$i])) {
break;
}
$code .= $string[$i]; $code .= $string[$i];
} }
// We have to be extremely careful when adding // We have to be extremely careful when adding
// new characters, to make sure we're not breaking // new characters, to make sure we're not breaking
// the encoding. // the encoding.
$char = HTMLPurifier_Encoder::unichr(hexdec($code)); $char = HTMLPurifier_Encoder::unichr(hexdec($code));
if (HTMLPurifier_Encoder::cleanUTF8($char) === '') continue; if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
$ret .= $char; continue;
if ($i < $c && trim($string[$i]) !== '') $i--; }
$ret .= $char;
if ($i < $c && trim($string[$i]) !== '') {
$i--;
}
continue;
}
if ($string[$i] === "\n") {
continue; continue;
} }
if ($string[$i] === "\n") continue;
} }
$ret .= $string[$i]; $ret .= $string[$i];
} }
return $ret; return $ret;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -14,8 +14,14 @@
class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
{ {
public function validate($css, $config, $context) { /**
* @param string $css
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($css, $config, $context)
{
$css = $this->parseCDATA($css); $css = $this->parseCDATA($css);
$definition = $config->getCSSDefinition(); $definition = $config->getCSSDefinition();
@ -36,8 +42,12 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
$context->register('CurrentCSSProperty', $property); $context->register('CurrentCSSProperty', $property);
foreach ($declarations as $declaration) { foreach ($declarations as $declaration) {
if (!$declaration) continue; if (!$declaration) {
if (!strpos($declaration, ':')) continue; continue;
}
if (!strpos($declaration, ':')) {
continue;
}
list($property, $value) = explode(':', $declaration, 2); list($property, $value) = explode(':', $declaration, 2);
$property = trim($property); $property = trim($property);
$value = trim($value); $value = trim($value);
@ -47,23 +57,32 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
$ok = true; $ok = true;
break; break;
} }
if (ctype_lower($property)) break; if (ctype_lower($property)) {
break;
}
$property = strtolower($property); $property = strtolower($property);
if (isset($definition->info[$property])) { if (isset($definition->info[$property])) {
$ok = true; $ok = true;
break; break;
} }
} while(0); } while (0);
if (!$ok) continue; if (!$ok) {
continue;
}
// inefficient call, since the validator will do this again // inefficient call, since the validator will do this again
if (strtolower(trim($value)) !== 'inherit') { if (strtolower(trim($value)) !== 'inherit') {
// inherit works for everything (but only on the base property) // inherit works for everything (but only on the base property)
$result = $definition->info[$property]->validate( $result = $definition->info[$property]->validate(
$value, $config, $context ); $value,
$config,
$context
);
} else { } else {
$result = 'inherit'; $result = 'inherit';
} }
if ($result === false) continue; if ($result === false) {
continue;
}
$propvalues[$property] = $result; $propvalues[$property] = $result;
} }

View File

@ -3,19 +3,32 @@
class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
{ {
public function __construct() { public function __construct()
{
parent::__construct(false); // opacity is non-negative, but we will clamp it parent::__construct(false); // opacity is non-negative, but we will clamp it
} }
public function validate($number, $config, $context) { /**
* @param string $number
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return string
*/
public function validate($number, $config, $context)
{
$result = parent::validate($number, $config, $context); $result = parent::validate($number, $config, $context);
if ($result === false) return $result; if ($result === false) {
$float = (float) $result; return $result;
if ($float < 0.0) $result = '0'; }
if ($float > 1.0) $result = '1'; $float = (float)$result;
if ($float < 0.0) {
$result = '0';
}
if ($float > 1.0) {
$result = '1';
}
return $result; return $result;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -9,11 +9,16 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
/** /**
* Local copy of component validators. * Local copy of component validators.
* @type HTMLPurifier_AttrDef[]
* @note See HTMLPurifier_AttrDef_Font::$info for a similar impl. * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
*/ */
protected $info; protected $info;
public function __construct($config) { /**
* @param HTMLPurifier_Config $config
*/
public function __construct($config)
{
$def = $config->getCSSDefinition(); $def = $config->getCSSDefinition();
$this->info['background-color'] = $def->info['background-color']; $this->info['background-color'] = $def->info['background-color'];
$this->info['background-image'] = $def->info['background-image']; $this->info['background-image'] = $def->info['background-image'];
@ -22,17 +27,25 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
$this->info['background-position'] = $def->info['background-position']; $this->info['background-position'] = $def->info['background-position'];
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
// regular pre-processing // regular pre-processing
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
if ($string === '') return false; if ($string === '') {
return false;
}
// munge rgb() decl if necessary // munge rgb() decl if necessary
$string = $this->mungeRgb($string); $string = $this->mungeRgb($string);
// assumes URI doesn't have spaces in it // assumes URI doesn't have spaces in it
$bits = explode(' ', strtolower($string)); // bits to process $bits = explode(' ', $string); // bits to process
$caught = array(); $caught = array();
$caught['color'] = false; $caught['color'] = false;
@ -42,20 +55,27 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
$caught['position'] = false; $caught['position'] = false;
$i = 0; // number of catches $i = 0; // number of catches
$none = false;
foreach ($bits as $bit) { foreach ($bits as $bit) {
if ($bit === '') continue; if ($bit === '') {
continue;
}
foreach ($caught as $key => $status) { foreach ($caught as $key => $status) {
if ($key != 'position') { if ($key != 'position') {
if ($status !== false) continue; if ($status !== false) {
continue;
}
$r = $this->info['background-' . $key]->validate($bit, $config, $context); $r = $this->info['background-' . $key]->validate($bit, $config, $context);
} else { } else {
$r = $bit; $r = $bit;
} }
if ($r === false) continue; if ($r === false) {
continue;
}
if ($key == 'position') { if ($key == 'position') {
if ($caught[$key] === false) $caught[$key] = ''; if ($caught[$key] === false) {
$caught[$key] = '';
}
$caught[$key] .= $r . ' '; $caught[$key] .= $r . ' ';
} else { } else {
$caught[$key] = $r; $caught[$key] = $r;
@ -65,7 +85,9 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
} }
} }
if (!$i) return false; if (!$i) {
return false;
}
if ($caught['position'] !== false) { if ($caught['position'] !== false) {
$caught['position'] = $this->info['background-position']-> $caught['position'] = $this->info['background-position']->
validate($caught['position'], $config, $context); validate($caught['position'], $config, $context);
@ -73,15 +95,17 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
$ret = array(); $ret = array();
foreach ($caught as $value) { foreach ($caught as $value) {
if ($value === false) continue; if ($value === false) {
continue;
}
$ret[] = $value; $ret[] = $value;
} }
if (empty($ret)) return false; if (empty($ret)) {
return implode(' ', $ret); return false;
}
return implode(' ', $ret);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -44,15 +44,30 @@
class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
{ {
/**
* @type HTMLPurifier_AttrDef_CSS_Length
*/
protected $length; protected $length;
/**
* @type HTMLPurifier_AttrDef_CSS_Percentage
*/
protected $percentage; protected $percentage;
public function __construct() { public function __construct()
{
$this->length = new HTMLPurifier_AttrDef_CSS_Length(); $this->length = new HTMLPurifier_AttrDef_CSS_Length();
$this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage(); $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
$bits = explode(' ', $string); $bits = explode(' ', $string);
@ -74,7 +89,9 @@ class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
); );
foreach ($bits as $bit) { foreach ($bits as $bit) {
if ($bit === '') continue; if ($bit === '') {
continue;
}
// test for keyword // test for keyword
$lbit = ctype_lower($bit) ? $bit : strtolower($bit); $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
@ -104,30 +121,37 @@ class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
$measures[] = $r; $measures[] = $r;
$i++; $i++;
} }
} }
if (!$i) return false; // no valid values were caught if (!$i) {
return false;
} // no valid values were caught
$ret = array(); $ret = array();
// first keyword // first keyword
if ($keywords['h']) $ret[] = $keywords['h']; if ($keywords['h']) {
elseif ($keywords['ch']) { $ret[] = $keywords['h'];
} elseif ($keywords['ch']) {
$ret[] = $keywords['ch']; $ret[] = $keywords['ch'];
$keywords['cv'] = false; // prevent re-use: center = center center $keywords['cv'] = false; // prevent re-use: center = center center
} elseif (count($measures)) {
$ret[] = array_shift($measures);
} }
elseif (count($measures)) $ret[] = array_shift($measures);
if ($keywords['v']) $ret[] = $keywords['v']; if ($keywords['v']) {
elseif ($keywords['cv']) $ret[] = $keywords['cv']; $ret[] = $keywords['v'];
elseif (count($measures)) $ret[] = array_shift($measures); } elseif ($keywords['cv']) {
$ret[] = $keywords['cv'];
} elseif (count($measures)) {
$ret[] = array_shift($measures);
}
if (empty($ret)) return false; if (empty($ret)) {
return false;
}
return implode(' ', $ret); return implode(' ', $ret);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -8,17 +8,29 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
/** /**
* Local copy of properties this property is shorthand for. * Local copy of properties this property is shorthand for.
* @type HTMLPurifier_AttrDef[]
*/ */
protected $info = array(); protected $info = array();
public function __construct($config) { /**
* @param HTMLPurifier_Config $config
*/
public function __construct($config)
{
$def = $config->getCSSDefinition(); $def = $config->getCSSDefinition();
$this->info['border-width'] = $def->info['border-width']; $this->info['border-width'] = $def->info['border-width'];
$this->info['border-style'] = $def->info['border-style']; $this->info['border-style'] = $def->info['border-style'];
$this->info['border-top-color'] = $def->info['border-top-color']; $this->info['border-top-color'] = $def->info['border-top-color'];
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
$string = $this->mungeRgb($string); $string = $this->mungeRgb($string);
$bits = explode(' ', $string); $bits = explode(' ', $string);
@ -26,7 +38,9 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
$ret = ''; // return value $ret = ''; // return value
foreach ($bits as $bit) { foreach ($bits as $bit) {
foreach ($this->info as $propname => $validator) { foreach ($this->info as $propname => $validator) {
if (isset($done[$propname])) continue; if (isset($done[$propname])) {
continue;
}
$r = $validator->validate($bit, $config, $context); $r = $validator->validate($bit, $config, $context);
if ($r !== false) { if ($r !== false) {
$ret .= $r . ' '; $ret .= $r . ' ';
@ -37,7 +51,6 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
} }
return rtrim($ret); return rtrim($ret);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,29 +6,47 @@
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
{ {
public function validate($color, $config, $context) { /**
* @param string $color
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($color, $config, $context)
{
static $colors = null; static $colors = null;
if ($colors === null) $colors = $config->get('Core.ColorKeywords'); if ($colors === null) {
$colors = $config->get('Core.ColorKeywords');
}
$color = trim($color); $color = trim($color);
if ($color === '') return false; if ($color === '') {
return false;
}
$lower = strtolower($color); $lower = strtolower($color);
if (isset($colors[$lower])) return $colors[$lower]; if (isset($colors[$lower])) {
return $colors[$lower];
}
if (strpos($color, 'rgb(') !== false) { if (strpos($color, 'rgb(') !== false) {
// rgb literal handling // rgb literal handling
$length = strlen($color); $length = strlen($color);
if (strpos($color, ')') !== $length - 1) return false; if (strpos($color, ')') !== $length - 1) {
return false;
}
$triad = substr($color, 4, $length - 4 - 1); $triad = substr($color, 4, $length - 4 - 1);
$parts = explode(',', $triad); $parts = explode(',', $triad);
if (count($parts) !== 3) return false; if (count($parts) !== 3) {
return false;
}
$type = false; // to ensure that they're all the same type $type = false; // to ensure that they're all the same type
$new_parts = array(); $new_parts = array();
foreach ($parts as $part) { foreach ($parts as $part) {
$part = trim($part); $part = trim($part);
if ($part === '') return false; if ($part === '') {
return false;
}
$length = strlen($part); $length = strlen($part);
if ($part[$length - 1] === '%') { if ($part[$length - 1] === '%') {
// handle percents // handle percents
@ -37,9 +55,13 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
} elseif ($type !== 'percentage') { } elseif ($type !== 'percentage') {
return false; return false;
} }
$num = (float) substr($part, 0, $length - 1); $num = (float)substr($part, 0, $length - 1);
if ($num < 0) $num = 0; if ($num < 0) {
if ($num > 100) $num = 100; $num = 0;
}
if ($num > 100) {
$num = 100;
}
$new_parts[] = "$num%"; $new_parts[] = "$num%";
} else { } else {
// handle integers // handle integers
@ -48,10 +70,14 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
} elseif ($type !== 'integer') { } elseif ($type !== 'integer') {
return false; return false;
} }
$num = (int) $part; $num = (int)$part;
if ($num < 0) $num = 0; if ($num < 0) {
if ($num > 255) $num = 255; $num = 0;
$new_parts[] = (string) $num; }
if ($num > 255) {
$num = 255;
}
$new_parts[] = (string)$num;
} }
} }
$new_triad = implode(',', $new_parts); $new_triad = implode(',', $new_parts);
@ -65,14 +91,15 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
$color = '#' . $color; $color = '#' . $color;
} }
$length = strlen($hex); $length = strlen($hex);
if ($length !== 3 && $length !== 6) return false; if ($length !== 3 && $length !== 6) {
if (!ctype_xdigit($hex)) return false; return false;
}
if (!ctype_xdigit($hex)) {
return false;
}
} }
return $color; return $color;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -13,26 +13,36 @@ class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
{ {
/** /**
* List of HTMLPurifier_AttrDef objects that may process strings * List of objects that may process strings.
* @type HTMLPurifier_AttrDef[]
* @todo Make protected * @todo Make protected
*/ */
public $defs; public $defs;
/** /**
* @param $defs List of HTMLPurifier_AttrDef objects * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects
*/ */
public function __construct($defs) { public function __construct($defs)
{
$this->defs = $defs; $this->defs = $defs;
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
foreach ($this->defs as $i => $def) { foreach ($this->defs as $i => $def) {
$result = $this->defs[$i]->validate($string, $config, $context); $result = $this->defs[$i]->validate($string, $config, $context);
if ($result !== false) return $result; if ($result !== false) {
return $result;
}
} }
return false; return false;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -5,22 +5,38 @@
*/ */
class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
{ {
public $def, $element; /**
* @type HTMLPurifier_AttrDef
*/
public $def;
/**
* @type string
*/
public $element;
/** /**
* @param $def Definition to wrap * @param HTMLPurifier_AttrDef $def Definition to wrap
* @param $element Element to deny * @param string $element Element to deny
*/ */
public function __construct($def, $element) { public function __construct($def, $element)
{
$this->def = $def; $this->def = $def;
$this->element = $element; $this->element = $element;
} }
/** /**
* Checks if CurrentToken is set and equal to $this->element * Checks if CurrentToken is set and equal to $this->element
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/ */
public function validate($string, $config, $context) { public function validate($string, $config, $context)
{
$token = $context->get('CurrentToken', true); $token = $context->get('CurrentToken', true);
if ($token && $token->name == $this->element) return false; if ($token && $token->name == $this->element) {
return false;
}
return $this->def->validate($string, $config, $context); return $this->def->validate($string, $config, $context);
} }
} }

View File

@ -7,23 +7,37 @@
*/ */
class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
{ {
/**
* @type HTMLPurifier_AttrDef_Integer
*/
protected $intValidator; protected $intValidator;
public function __construct() { public function __construct()
{
$this->intValidator = new HTMLPurifier_AttrDef_Integer(); $this->intValidator = new HTMLPurifier_AttrDef_Integer();
} }
public function validate($value, $config, $context) { /**
* @param string $value
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($value, $config, $context)
{
$value = $this->parseCDATA($value); $value = $this->parseCDATA($value);
if ($value === 'none') return $value; if ($value === 'none') {
return $value;
}
// if we looped this we could support multiple filters // if we looped this we could support multiple filters
$function_length = strcspn($value, '('); $function_length = strcspn($value, '(');
$function = trim(substr($value, 0, $function_length)); $function = trim(substr($value, 0, $function_length));
if ($function !== 'alpha' && if ($function !== 'alpha' &&
$function !== 'Alpha' && $function !== 'Alpha' &&
$function !== 'progid:DXImageTransform.Microsoft.Alpha' $function !== 'progid:DXImageTransform.Microsoft.Alpha'
) return false; ) {
return false;
}
$cursor = $function_length + 1; $cursor = $function_length + 1;
$parameters_length = strcspn($value, ')', $cursor); $parameters_length = strcspn($value, ')', $cursor);
$parameters = substr($value, $cursor, $parameters_length); $parameters = substr($value, $cursor, $parameters_length);
@ -34,13 +48,23 @@ class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
list($key, $value) = explode('=', $param); list($key, $value) = explode('=', $param);
$key = trim($key); $key = trim($key);
$value = trim($value); $value = trim($value);
if (isset($lookup[$key])) continue; if (isset($lookup[$key])) {
if ($key !== 'opacity') continue; continue;
}
if ($key !== 'opacity') {
continue;
}
$value = $this->intValidator->validate($value, $config, $context); $value = $this->intValidator->validate($value, $config, $context);
if ($value === false) continue; if ($value === false) {
$int = (int) $value; continue;
if ($int > 100) $value = '100'; }
if ($int < 0) $value = '0'; $int = (int)$value;
if ($int > 100) {
$value = '100';
}
if ($int < 0) {
$value = '0';
}
$ret_params[] = "$key=$value"; $ret_params[] = "$key=$value";
$lookup[$key] = true; $lookup[$key] = true;
} }
@ -48,7 +72,6 @@ class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
$ret_function = "$function($ret_parameters)"; $ret_function = "$function($ret_parameters)";
return $ret_function; return $ret_function;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,8 +7,8 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
{ {
/** /**
* Local copy of component validators. * Local copy of validators
* * @type HTMLPurifier_AttrDef[]
* @note If we moved specific CSS property definitions to their own * @note If we moved specific CSS property definitions to their own
* classes instead of having them be assembled at run time by * classes instead of having them be assembled at run time by
* CSSDefinition, this wouldn't be necessary. We'd instantiate * CSSDefinition, this wouldn't be necessary. We'd instantiate
@ -16,7 +16,11 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
*/ */
protected $info = array(); protected $info = array();
public function __construct($config) { /**
* @param HTMLPurifier_Config $config
*/
public function __construct($config)
{
$def = $config->getCSSDefinition(); $def = $config->getCSSDefinition();
$this->info['font-style'] = $def->info['font-style']; $this->info['font-style'] = $def->info['font-style'];
$this->info['font-variant'] = $def->info['font-variant']; $this->info['font-variant'] = $def->info['font-variant'];
@ -26,8 +30,14 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
$this->info['font-family'] = $def->info['font-family']; $this->info['font-family'] = $def->info['font-family'];
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
static $system_fonts = array( static $system_fonts = array(
'caption' => true, 'caption' => true,
'icon' => true, 'icon' => true,
@ -39,7 +49,9 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
// regular pre-processing // regular pre-processing
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
if ($string === '') return false; if ($string === '') {
return false;
}
// check if it's one of the keywords // check if it's one of the keywords
$lowercase_string = strtolower($string); $lowercase_string = strtolower($string);
@ -54,15 +66,20 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
$final = ''; // output $final = ''; // output
for ($i = 0, $size = count($bits); $i < $size; $i++) { for ($i = 0, $size = count($bits); $i < $size; $i++) {
if ($bits[$i] === '') continue; if ($bits[$i] === '') {
continue;
}
switch ($stage) { switch ($stage) {
case 0: // attempting to catch font-style, font-variant or font-weight
// attempting to catch font-style, font-variant or font-weight
case 0:
foreach ($stage_1 as $validator_name) { foreach ($stage_1 as $validator_name) {
if (isset($caught[$validator_name])) continue; if (isset($caught[$validator_name])) {
continue;
}
$r = $this->info[$validator_name]->validate( $r = $this->info[$validator_name]->validate(
$bits[$i], $config, $context); $bits[$i],
$config,
$context
);
if ($r !== false) { if ($r !== false) {
$final .= $r . ' '; $final .= $r . ' ';
$caught[$validator_name] = true; $caught[$validator_name] = true;
@ -70,11 +87,13 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
} }
} }
// all three caught, continue on // all three caught, continue on
if (count($caught) >= 3) $stage = 1; if (count($caught) >= 3) {
if ($r !== false) break; $stage = 1;
}
// attempting to catch font-size and perhaps line-height if ($r !== false) {
case 1: break;
}
case 1: // attempting to catch font-size and perhaps line-height
$found_slash = false; $found_slash = false;
if (strpos($bits[$i], '/') !== false) { if (strpos($bits[$i], '/') !== false) {
list($font_size, $line_height) = list($font_size, $line_height) =
@ -89,14 +108,19 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
$line_height = false; $line_height = false;
} }
$r = $this->info['font-size']->validate( $r = $this->info['font-size']->validate(
$font_size, $config, $context); $font_size,
$config,
$context
);
if ($r !== false) { if ($r !== false) {
$final .= $r; $final .= $r;
// attempt to catch line-height // attempt to catch line-height
if ($line_height === false) { if ($line_height === false) {
// we need to scroll forward // we need to scroll forward
for ($j = $i + 1; $j < $size; $j++) { for ($j = $i + 1; $j < $size; $j++) {
if ($bits[$j] === '') continue; if ($bits[$j] === '') {
continue;
}
if ($bits[$j] === '/') { if ($bits[$j] === '/') {
if ($found_slash) { if ($found_slash) {
return false; return false;
@ -116,7 +140,10 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
if ($found_slash) { if ($found_slash) {
$i = $j; $i = $j;
$r = $this->info['line-height']->validate( $r = $this->info['line-height']->validate(
$line_height, $config, $context); $line_height,
$config,
$context
);
if ($r !== false) { if ($r !== false) {
$final .= '/' . $r; $final .= '/' . $r;
} }
@ -126,13 +153,14 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
break; break;
} }
return false; return false;
case 2: // attempting to catch font-family
// attempting to catch font-family
case 2:
$font_family = $font_family =
implode(' ', array_slice($bits, $i, $size - $i)); implode(' ', array_slice($bits, $i, $size - $i));
$r = $this->info['font-family']->validate( $r = $this->info['font-family']->validate(
$font_family, $config, $context); $font_family,
$config,
$context
);
if ($r !== false) { if ($r !== false) {
$final .= $r . ' '; $final .= $r . ' ';
// processing completed successfully // processing completed successfully
@ -143,7 +171,6 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
} }
return false; return false;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -2,12 +2,58 @@
/** /**
* Validates a font family list according to CSS spec * Validates a font family list according to CSS spec
* @todo whitelisting allowed fonts would be nice
*/ */
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
{ {
public function validate($string, $config, $context) { protected $mask = null;
public function __construct()
{
$this->mask = '_- ';
for ($c = 'a'; $c <= 'z'; $c++) {
$this->mask .= $c;
}
for ($c = 'A'; $c <= 'Z'; $c++) {
$this->mask .= $c;
}
for ($c = '0'; $c <= '9'; $c++) {
$this->mask .= $c;
} // cast-y, but should be fine
// special bytes used by UTF-8
for ($i = 0x80; $i <= 0xFF; $i++) {
// We don't bother excluding invalid bytes in this range,
// because the our restriction of well-formed UTF-8 will
// prevent these from ever occurring.
$this->mask .= chr($i);
}
/*
PHP's internal strcspn implementation is
O(length of string * length of mask), making it inefficient
for large masks. However, it's still faster than
preg_match 8)
for (p = s1;;) {
spanp = s2;
do {
if (*spanp == c || p == s1_end) {
return p - s1;
}
} while (spanp++ < (s2_end - 1));
c = *++p;
}
*/
// possible optimization: invert the mask.
}
/**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
static $generic_names = array( static $generic_names = array(
'serif' => true, 'serif' => true,
'sans-serif' => true, 'sans-serif' => true,
@ -15,24 +61,33 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
'fantasy' => true, 'fantasy' => true,
'cursive' => true 'cursive' => true
); );
$allowed_fonts = $config->get('CSS.AllowedFonts');
// assume that no font names contain commas in them // assume that no font names contain commas in them
$fonts = explode(',', $string); $fonts = explode(',', $string);
$final = ''; $final = '';
foreach($fonts as $font) { foreach ($fonts as $font) {
$font = trim($font); $font = trim($font);
if ($font === '') continue; if ($font === '') {
continue;
}
// match a generic name // match a generic name
if (isset($generic_names[$font])) { if (isset($generic_names[$font])) {
if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
$final .= $font . ', '; $final .= $font . ', ';
}
continue; continue;
} }
// match a quoted name // match a quoted name
if ($font[0] === '"' || $font[0] === "'") { if ($font[0] === '"' || $font[0] === "'") {
$length = strlen($font); $length = strlen($font);
if ($length <= 2) continue; if ($length <= 2) {
continue;
}
$quote = $font[0]; $quote = $font[0];
if ($font[$length - 1] !== $quote) continue; if ($font[$length - 1] !== $quote) {
continue;
}
$font = substr($font, 1, $length - 2); $font = substr($font, 1, $length - 2);
} }
@ -40,6 +95,10 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
// $font is a pure representation of the font name // $font is a pure representation of the font name
if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
continue;
}
if (ctype_alnum($font) && $font !== '') { if (ctype_alnum($font) && $font !== '') {
// very simple font, allow it in unharmed // very simple font, allow it in unharmed
$final .= $font . ', '; $final .= $font . ', ';
@ -50,20 +109,108 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
// shouldn't show up regardless // shouldn't show up regardless
$font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font); $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
// These ugly transforms don't pose a security // Here, there are various classes of characters which need
// risk (as \\ and \" might). We could try to be clever and // to be treated differently:
// use single-quote wrapping when there is a double quote // - Alphanumeric characters are essentially safe. We
// present, but I have choosen not to implement that. // handled these above.
// (warning: this code relies on the selection of quotation // - Spaces require quoting, though most parsers will do
// mark below) // the right thing if there aren't any characters that
$font = str_replace('\\', '\\5C ', $font); // can be misinterpreted
$font = str_replace('"', '\\22 ', $font); // - Dashes rarely occur, but they fairly unproblematic
// for parsing/rendering purposes.
// The above characters cover the majority of Western font
// names.
// - Arbitrary Unicode characters not in ASCII. Because
// most parsers give little thought to Unicode, treatment
// of these codepoints is basically uniform, even for
// punctuation-like codepoints. These characters can
// show up in non-Western pages and are supported by most
// major browsers, for example: " 明朝" is a
// legitimate font-name
// <http://ja.wikipedia.org/wiki/MS_明朝>. See
// the CSS3 spec for more examples:
// <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
// You can see live samples of these on the Internet:
// <http://www.google.co.jp/search?q=font-family++明朝|ゴシック>
// However, most of these fonts have ASCII equivalents:
// for example, 'MS Mincho', and it's considered
// professional to use ASCII font names instead of
// Unicode font names. Thanks Takeshi Terada for
// providing this information.
// The following characters, to my knowledge, have not been
// used to name font names.
// - Single quote. While theoretically you might find a
// font name that has a single quote in its name (serving
// as an apostrophe, e.g. Dave's Scribble), I haven't
// been able to find any actual examples of this.
// Internet Explorer's cssText translation (which I
// believe is invoked by innerHTML) normalizes any
// quoting to single quotes, and fails to escape single
// quotes. (Note that this is not IE's behavior for all
// CSS properties, just some sort of special casing for
// font-family). So a single quote *cannot* be used
// safely in the font-family context if there will be an
// innerHTML/cssText translation. Note that Firefox 3.x
// does this too.
// - Double quote. In IE, these get normalized to
// single-quotes, no matter what the encoding. (Fun
// fact, in IE8, the 'content' CSS property gained
// support, where they special cased to preserve encoded
// double quotes, but still translate unadorned double
// quotes into single quotes.) So, because their
// fixpoint behavior is identical to single quotes, they
// cannot be allowed either. Firefox 3.x displays
// single-quote style behavior.
// - Backslashes are reduced by one (so \\ -> \) every
// iteration, so they cannot be used safely. This shows
// up in IE7, IE8 and FF3
// - Semicolons, commas and backticks are handled properly.
// - The rest of the ASCII punctuation is handled properly.
// We haven't checked what browsers do to unadorned
// versions, but this is not important as long as the
// browser doesn't /remove/ surrounding quotes (as IE does
// for HTML).
//
// With these results in hand, we conclude that there are
// various levels of safety:
// - Paranoid: alphanumeric, spaces and dashes(?)
// - International: Paranoid + non-ASCII Unicode
// - Edgy: Everything except quotes, backslashes
// - NoJS: Standards compliance, e.g. sod IE. Note that
// with some judicious character escaping (since certain
// types of escaping doesn't work) this is theoretically
// OK as long as innerHTML/cssText is not called.
// We believe that international is a reasonable default
// (that we will implement now), and once we do more
// extensive research, we may feel comfortable with dropping
// it down to edgy.
// complicated font, requires quoting // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of
$final .= "\"$font\", "; // note that this will later get turned into &quot; // str(c)spn assumes that the string was already well formed
// Unicode (which of course it is).
if (strspn($font, $this->mask) !== strlen($font)) {
continue;
}
// Historical:
// In the absence of innerHTML/cssText, these ugly
// transforms don't pose a security risk (as \\ and \"
// might--these escapes are not supported by most browsers).
// We could try to be clever and use single-quote wrapping
// when there is a double quote present, but I have choosen
// not to implement that. (NOTE: you can reduce the amount
// of escapes by one depending on what quoting style you use)
// $font = str_replace('\\', '\\5C ', $font);
// $font = str_replace('"', '\\22 ', $font);
// $font = str_replace("'", '\\27 ', $font);
// font possibly with spaces, requires quoting
$final .= "'$font', ";
} }
$final = rtrim($final, ', '); $final = rtrim($final, ', ');
if ($final === '') return false; if ($final === '') {
return false;
}
return $final; return $final;
} }

View File

@ -0,0 +1,32 @@
<?php
/**
* Validates based on {ident} CSS grammar production
*/
class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
{
/**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = trim($string);
// early abort: '' and '0' (strings that convert to false) are invalid
if (!$string) {
return false;
}
$pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
if (!preg_match($pattern, $string)) {
return false;
}
return $string;
}
}
// vim: et sw=4 sts=4

View File

@ -5,20 +5,34 @@
*/ */
class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
{ {
public $def, $allow; /**
* @type HTMLPurifier_AttrDef
*/
public $def;
/**
* @type bool
*/
public $allow;
/** /**
* @param $def Definition to wrap * @param HTMLPurifier_AttrDef $def Definition to wrap
* @param $allow Whether or not to allow !important * @param bool $allow Whether or not to allow !important
*/ */
public function __construct($def, $allow = false) { public function __construct($def, $allow = false)
{
$this->def = $def; $this->def = $def;
$this->allow = $allow; $this->allow = $allow;
} }
/** /**
* Intercepts and removes !important if necessary * Intercepts and removes !important if necessary
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/ */
public function validate($string, $config, $context) { public function validate($string, $config, $context)
{
// test for ! and important tokens // test for ! and important tokens
$string = trim($string); $string = trim($string);
$is_important = false; $is_important = false;
@ -32,7 +46,9 @@ class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
} }
} }
$string = $this->def->validate($string, $config, $context); $string = $this->def->validate($string, $config, $context);
if ($this->allow && $is_important) $string .= ' !important'; if ($this->allow && $is_important) {
$string .= ' !important';
}
return $string; return $string;
} }
} }

View File

@ -6,42 +6,72 @@
class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
{ {
protected $min, $max; /**
* @type HTMLPurifier_Length|string
*/
protected $min;
/** /**
* @param HTMLPurifier_Length $max Minimum length, or null for no bound. String is also acceptable. * @type HTMLPurifier_Length|string
* @param HTMLPurifier_Length $max Maximum length, or null for no bound. String is also acceptable.
*/ */
public function __construct($min = null, $max = null) { protected $max;
/**
* @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable.
* @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable.
*/
public function __construct($min = null, $max = null)
{
$this->min = $min !== null ? HTMLPurifier_Length::make($min) : null; $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
$this->max = $max !== null ? HTMLPurifier_Length::make($max) : null; $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
// Optimizations // Optimizations
if ($string === '') return false; if ($string === '') {
if ($string === '0') return '0'; return false;
if (strlen($string) === 1) return false; }
if ($string === '0') {
return '0';
}
if (strlen($string) === 1) {
return false;
}
$length = HTMLPurifier_Length::make($string); $length = HTMLPurifier_Length::make($string);
if (!$length->isValid()) return false; if (!$length->isValid()) {
return false;
}
if ($this->min) { if ($this->min) {
$c = $length->compareTo($this->min); $c = $length->compareTo($this->min);
if ($c === false) return false; if ($c === false) {
if ($c < 0) return false; return false;
}
if ($c < 0) {
return false;
}
} }
if ($this->max) { if ($this->max) {
$c = $length->compareTo($this->max); $c = $length->compareTo($this->max);
if ($c === false) return false; if ($c === false) {
if ($c > 0) return false; return false;
}
if ($c > 0) {
return false;
}
} }
return $length->toString(); return $length->toString();
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -8,23 +8,36 @@ class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
{ {
/** /**
* Local copy of component validators. * Local copy of validators.
* @type HTMLPurifier_AttrDef[]
* @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl. * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
*/ */
protected $info; protected $info;
public function __construct($config) { /**
* @param HTMLPurifier_Config $config
*/
public function __construct($config)
{
$def = $config->getCSSDefinition(); $def = $config->getCSSDefinition();
$this->info['list-style-type'] = $def->info['list-style-type']; $this->info['list-style-type'] = $def->info['list-style-type'];
$this->info['list-style-position'] = $def->info['list-style-position']; $this->info['list-style-position'] = $def->info['list-style-position'];
$this->info['list-style-image'] = $def->info['list-style-image']; $this->info['list-style-image'] = $def->info['list-style-image'];
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
// regular pre-processing // regular pre-processing
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
if ($string === '') return false; if ($string === '') {
return false;
}
// assumes URI doesn't have spaces in it // assumes URI doesn't have spaces in it
$bits = explode(' ', strtolower($string)); // bits to process $bits = explode(' ', strtolower($string)); // bits to process
@ -38,16 +51,29 @@ class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
$none = false; $none = false;
foreach ($bits as $bit) { foreach ($bits as $bit) {
if ($i >= 3) return; // optimization bit if ($i >= 3) {
if ($bit === '') continue; return;
} // optimization bit
if ($bit === '') {
continue;
}
foreach ($caught as $key => $status) { foreach ($caught as $key => $status) {
if ($status !== false) continue; if ($status !== false) {
continue;
}
$r = $this->info['list-style-' . $key]->validate($bit, $config, $context); $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
if ($r === false) continue; if ($r === false) {
continue;
}
if ($r === 'none') { if ($r === 'none') {
if ($none) continue; if ($none) {
else $none = true; continue;
if ($key == 'image') continue; } else {
$none = true;
}
if ($key == 'image') {
continue;
}
} }
$caught[$key] = $r; $caught[$key] = $r;
$i++; $i++;
@ -55,24 +81,32 @@ class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
} }
} }
if (!$i) return false; if (!$i) {
return false;
}
$ret = array(); $ret = array();
// construct type // construct type
if ($caught['type']) $ret[] = $caught['type']; if ($caught['type']) {
$ret[] = $caught['type'];
// construct image
if ($caught['image']) $ret[] = $caught['image'];
// construct position
if ($caught['position']) $ret[] = $caught['position'];
if (empty($ret)) return false;
return implode(' ', $ret);
} }
// construct image
if ($caught['image']) {
$ret[] = $caught['image'];
}
// construct position
if ($caught['position']) {
$ret[] = $caught['position'];
}
if (empty($ret)) {
return false;
}
return implode(' ', $ret);
}
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -13,9 +13,9 @@
*/ */
class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
{ {
/** /**
* Instance of component definition to defer validation to. * Instance of component definition to defer validation to.
* @type HTMLPurifier_AttrDef
* @todo Make protected * @todo Make protected
*/ */
public $single; public $single;
@ -27,32 +27,45 @@ class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
public $max; public $max;
/** /**
* @param $single HTMLPurifier_AttrDef to multiply * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply
* @param $max Max number of values allowed (usually four) * @param int $max Max number of values allowed (usually four)
*/ */
public function __construct($single, $max = 4) { public function __construct($single, $max = 4)
{
$this->single = $single; $this->single = $single;
$this->max = $max; $this->max = $max;
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
if ($string === '') return false; if ($string === '') {
return false;
}
$parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
$length = count($parts); $length = count($parts);
$final = ''; $final = '';
for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) { for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
if (ctype_space($parts[$i])) continue; if (ctype_space($parts[$i])) {
continue;
}
$result = $this->single->validate($parts[$i], $config, $context); $result = $this->single->validate($parts[$i], $config, $context);
if ($result !== false) { if ($result !== false) {
$final .= $result . ' '; $final .= $result . ' ';
$num++; $num++;
} }
} }
if ($final === '') return false; if ($final === '') {
return false;
}
return rtrim($final); return rtrim($final);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,32 +7,44 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
{ {
/** /**
* Bool indicating whether or not only positive values allowed. * Indicates whether or not only positive values are allowed.
* @type bool
*/ */
protected $non_negative = false; protected $non_negative = false;
/** /**
* @param $non_negative Bool indicating whether negatives are forbidden * @param bool $non_negative indicates whether negatives are forbidden
*/ */
public function __construct($non_negative = false) { public function __construct($non_negative = false)
{
$this->non_negative = $non_negative; $this->non_negative = $non_negative;
} }
/** /**
* @param string $number
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return string|bool
* @warning Some contexts do not pass $config, $context. These * @warning Some contexts do not pass $config, $context. These
* variables should not be used without checking HTMLPurifier_Length * variables should not be used without checking HTMLPurifier_Length
*/ */
public function validate($number, $config, $context) { public function validate($number, $config, $context)
{
$number = $this->parseCDATA($number); $number = $this->parseCDATA($number);
if ($number === '') return false; if ($number === '') {
if ($number === '0') return '0'; return false;
}
if ($number === '0') {
return '0';
}
$sign = ''; $sign = '';
switch ($number[0]) { switch ($number[0]) {
case '-': case '-':
if ($this->non_negative) return false; if ($this->non_negative) {
return false;
}
$sign = '-'; $sign = '-';
case '+': case '+':
$number = substr($number, 1); $number = substr($number, 1);
@ -44,12 +56,18 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
} }
// Period is the only non-numeric character allowed // Period is the only non-numeric character allowed
if (strpos($number, '.') === false) return false; if (strpos($number, '.') === false) {
return false;
}
list($left, $right) = explode('.', $number, 2); list($left, $right) = explode('.', $number, 2);
if ($left === '' && $right === '') return false; if ($left === '' && $right === '') {
if ($left !== '' && !ctype_digit($left)) return false; return false;
}
if ($left !== '' && !ctype_digit($left)) {
return false;
}
$left = ltrim($left, '0'); $left = ltrim($left, '0');
$right = rtrim($right, '0'); $right = rtrim($right, '0');
@ -59,11 +77,8 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
} elseif (!ctype_digit($right)) { } elseif (!ctype_digit($right)) {
return false; return false;
} }
return $sign . $left . '.' . $right; return $sign . $left . '.' . $right;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,34 +7,48 @@ class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
{ {
/** /**
* Instance of HTMLPurifier_AttrDef_CSS_Number to defer number validation * Instance to defer number validation to.
* @type HTMLPurifier_AttrDef_CSS_Number
*/ */
protected $number_def; protected $number_def;
/** /**
* @param Bool indicating whether to forbid negative values * @param bool $non_negative Whether to forbid negative values
*/ */
public function __construct($non_negative = false) { public function __construct($non_negative = false)
{
$this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative); $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
if ($string === '') return false; if ($string === '') {
return false;
}
$length = strlen($string); $length = strlen($string);
if ($length === 1) return false; if ($length === 1) {
if ($string[$length - 1] !== '%') return false; return false;
}
if ($string[$length - 1] !== '%') {
return false;
}
$number = substr($string, 0, $length - 1); $number = substr($string, 0, $length - 1);
$number = $this->number_def->validate($number, $config, $context); $number = $this->number_def->validate($number, $config, $context);
if ($number === false) return false; if ($number === false) {
return "$number%"; return false;
}
return "$number%";
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -8,8 +8,14 @@
class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
{ {
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
static $allowed_values = array( static $allowed_values = array(
'line-through' => true, 'line-through' => true,
'overline' => true, 'overline' => true,
@ -18,7 +24,9 @@ class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
$string = strtolower($this->parseCDATA($string)); $string = strtolower($this->parseCDATA($string));
if ($string === 'none') return $string; if ($string === 'none') {
return $string;
}
$parts = explode(' ', $string); $parts = explode(' ', $string);
$final = ''; $final = '';
@ -28,11 +36,11 @@ class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
} }
} }
$final = rtrim($final); $final = rtrim($final);
if ($final === '') return false; if ($final === '') {
return $final; return false;
}
return $final;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -12,25 +12,39 @@
class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
{ {
public function __construct() { public function __construct()
{
parent::__construct(true); // always embedded parent::__construct(true); // always embedded
} }
public function validate($uri_string, $config, $context) { /**
* @param string $uri_string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($uri_string, $config, $context)
{
// parse the URI out of the string and then pass it onto // parse the URI out of the string and then pass it onto
// the parent object // the parent object
$uri_string = $this->parseCDATA($uri_string); $uri_string = $this->parseCDATA($uri_string);
if (strpos($uri_string, 'url(') !== 0) return false; if (strpos($uri_string, 'url(') !== 0) {
return false;
}
$uri_string = substr($uri_string, 4); $uri_string = substr($uri_string, 4);
$new_length = strlen($uri_string) - 1; $new_length = strlen($uri_string) - 1;
if ($uri_string[$new_length] != ')') return false; if ($uri_string[$new_length] != ')') {
return false;
}
$uri = trim(substr($uri_string, 0, $new_length)); $uri = trim(substr($uri_string, 0, $new_length));
if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) { if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
$quote = $uri[0]; $quote = $uri[0];
$new_length = strlen($uri) - 1; $new_length = strlen($uri) - 1;
if ($uri[$new_length] !== $quote) return false; if ($uri[$new_length] !== $quote) {
return false;
}
$uri = substr($uri, 1, $new_length - 1); $uri = substr($uri, 1, $new_length - 1);
} }
@ -38,15 +52,23 @@ class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
$result = parent::validate($uri, $config, $context); $result = parent::validate($uri, $config, $context);
if ($result === false) return false; if ($result === false) {
return false;
}
// extra sanity check; should have been done by URI // extra sanity check; should have been done by URI
$result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result); $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
// suspicious characters are ()'; we're going to percent encode
// them for safety.
$result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
// there's an extra bug where ampersands lose their escaping on
// an innerHTML cycle, so a very unlucky query parameter could
// then change the meaning of the URL. Unfortunately, there's
// not much we can do about that...
return "url(\"$result\")"; return "url(\"$result\")";
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -0,0 +1,44 @@
<?php
/**
* Dummy AttrDef that mimics another AttrDef, BUT it generates clones
* with make.
*/
class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
{
/**
* What we're cloning.
* @type HTMLPurifier_AttrDef
*/
protected $clone;
/**
* @param HTMLPurifier_AttrDef $clone
*/
public function __construct($clone)
{
$this->clone = $clone;
}
/**
* @param string $v
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($v, $config, $context)
{
return $this->clone->validate($v, $config, $context);
}
/**
* @param string $string
* @return HTMLPurifier_AttrDef
*/
public function make($string)
{
return clone $this->clone;
}
}
// vim: et sw=4 sts=4

View File

@ -12,6 +12,7 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
/** /**
* Lookup table of valid values. * Lookup table of valid values.
* @type array
* @todo Make protected * @todo Make protected
*/ */
public $valid_values = array(); public $valid_values = array();
@ -23,17 +24,23 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
protected $case_sensitive = false; // values according to W3C spec protected $case_sensitive = false; // values according to W3C spec
/** /**
* @param $valid_values List of valid values * @param array $valid_values List of valid values
* @param $case_sensitive Bool indicating whether or not case sensitive * @param bool $case_sensitive Whether or not case sensitive
*/ */
public function __construct( public function __construct($valid_values = array(), $case_sensitive = false)
$valid_values = array(), $case_sensitive = false {
) {
$this->valid_values = array_flip($valid_values); $this->valid_values = array_flip($valid_values);
$this->case_sensitive = $case_sensitive; $this->case_sensitive = $case_sensitive;
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = trim($string); $string = trim($string);
if (!$this->case_sensitive) { if (!$this->case_sensitive) {
// we may want to do full case-insensitive libraries // we may want to do full case-insensitive libraries
@ -45,11 +52,13 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
} }
/** /**
* @param $string In form of comma-delimited list of case-insensitive * @param string $string In form of comma-delimited list of case-insensitive
* valid values. Example: "foo,bar,baz". Prepend "s:" to make * valid values. Example: "foo,bar,baz". Prepend "s:" to make
* case sensitive * case sensitive
* @return HTMLPurifier_AttrDef_Enum
*/ */
public function make($string) { public function make($string)
{
if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') { if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
$string = substr($string, 2); $string = substr($string, 2);
$sensitive = true; $sensitive = true;
@ -59,7 +68,6 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
$values = explode(',', $string); $values = explode(',', $string);
return new HTMLPurifier_AttrDef_Enum($values, $sensitive); return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,23 +6,46 @@
class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
{ {
/**
* @type bool
*/
protected $name; protected $name;
/**
* @type bool
*/
public $minimized = true; public $minimized = true;
public function __construct($name = false) {$this->name = $name;} /**
* @param bool $name
*/
public function __construct($name = false)
{
$this->name = $name;
}
public function validate($string, $config, $context) { /**
if (empty($string)) return false; * @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
if (empty($string)) {
return false;
}
return $this->name; return $this->name;
} }
/** /**
* @param $string Name of attribute * @param string $string Name of attribute
* @return HTMLPurifier_AttrDef_HTML_Bool
*/ */
public function make($string) { public function make($string)
{
return new HTMLPurifier_AttrDef_HTML_Bool($string); return new HTMLPurifier_AttrDef_HTML_Bool($string);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -5,7 +5,14 @@
*/ */
class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
{ {
protected function split($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
protected function split($string, $config, $context)
{
// really, this twiddle should be lazy loaded // really, this twiddle should be lazy loaded
$name = $config->getDefinition('HTML')->doctype->name; $name = $config->getDefinition('HTML')->doctype->name;
if ($name == "XHTML 1.1" || $name == "XHTML 2.0") { if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
@ -14,13 +21,20 @@ class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
return preg_split('/\s+/', $string); return preg_split('/\s+/', $string);
} }
} }
protected function filter($tokens, $config, $context) {
/**
* @param array $tokens
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
protected function filter($tokens, $config, $context)
{
$allowed = $config->get('Attr.AllowedClasses'); $allowed = $config->get('Attr.AllowedClasses');
$forbidden = $config->get('Attr.ForbiddenClasses'); $forbidden = $config->get('Attr.ForbiddenClasses');
$ret = array(); $ret = array();
foreach ($tokens as $token) { foreach ($tokens as $token) {
if ( if (($allowed === null || isset($allowed[$token])) &&
($allowed === null || isset($allowed[$token])) &&
!isset($forbidden[$token]) && !isset($forbidden[$token]) &&
// We need this O(n) check because of PHP's array // We need this O(n) check because of PHP's array
// implementation that casts -0 to 0. // implementation that casts -0 to 0.

View File

@ -6,27 +6,46 @@
class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
{ {
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
static $colors = null; static $colors = null;
if ($colors === null) $colors = $config->get('Core.ColorKeywords'); if ($colors === null) {
$colors = $config->get('Core.ColorKeywords');
}
$string = trim($string); $string = trim($string);
if (empty($string)) return false; if (empty($string)) {
if (isset($colors[$string])) return $colors[$string]; return false;
if ($string[0] === '#') $hex = substr($string, 1); }
else $hex = $string; $lower = strtolower($string);
if (isset($colors[$lower])) {
$length = strlen($hex); return $colors[$lower];
if ($length !== 3 && $length !== 6) return false; }
if (!ctype_xdigit($hex)) return false; if ($string[0] === '#') {
if ($length === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2]; $hex = substr($string, 1);
} else {
return "#$hex"; $hex = $string;
} }
$length = strlen($hex);
if ($length !== 3 && $length !== 6) {
return false;
}
if (!ctype_xdigit($hex)) {
return false;
}
if ($length === 3) {
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
}
return "#$hex";
}
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,16 +6,33 @@
class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
{ {
/**
* @type array
*/
public $valid_values = false; // uninitialized value public $valid_values = false; // uninitialized value
/**
* @type bool
*/
protected $case_sensitive = false; protected $case_sensitive = false;
public function __construct() {} public function __construct()
{
public function validate($string, $config, $context) {
if ($this->valid_values === false) $this->valid_values = $config->get('Attr.AllowedFrameTargets');
return parent::validate($string, $config, $context);
} }
/**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
if ($this->valid_values === false) {
$this->valid_values = $config->get('Attr.AllowedFrameTargets');
}
return parent::validate($string, $config, $context);
}
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -12,39 +12,74 @@
class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
{ {
// ref functionality disabled, since we also have to verify // selector is NOT a valid thing to use for IDREFs, because IDREFs
// whether or not the ID it refers to exists // *must* target IDs that exist, whereas selector #ids do not.
public function validate($id, $config, $context) { /**
* Determines whether or not we're validating an ID in a CSS
* selector context.
* @type bool
*/
protected $selector;
if (!$config->get('Attr.EnableID')) return false; /**
* @param bool $selector
*/
public function __construct($selector = false)
{
$this->selector = $selector;
}
/**
* @param string $id
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($id, $config, $context)
{
if (!$this->selector && !$config->get('Attr.EnableID')) {
return false;
}
$id = trim($id); // trim it first $id = trim($id); // trim it first
if ($id === '') return false; if ($id === '') {
return false;
}
$prefix = $config->get('Attr.IDPrefix'); $prefix = $config->get('Attr.IDPrefix');
if ($prefix !== '') { if ($prefix !== '') {
$prefix .= $config->get('Attr.IDPrefixLocal'); $prefix .= $config->get('Attr.IDPrefixLocal');
// prevent re-appending the prefix // prevent re-appending the prefix
if (strpos($id, $prefix) !== 0) $id = $prefix . $id; if (strpos($id, $prefix) !== 0) {
$id = $prefix . $id;
}
} elseif ($config->get('Attr.IDPrefixLocal') !== '') { } elseif ($config->get('Attr.IDPrefixLocal') !== '') {
trigger_error('%Attr.IDPrefixLocal cannot be used unless '. trigger_error(
'%Attr.IDPrefix is set', E_USER_WARNING); '%Attr.IDPrefixLocal cannot be used unless ' .
'%Attr.IDPrefix is set',
E_USER_WARNING
);
} }
//if (!$this->ref) { if (!$this->selector) {
$id_accumulator =& $context->get('IDAccumulator'); $id_accumulator =& $context->get('IDAccumulator');
if (isset($id_accumulator->ids[$id])) return false; if (isset($id_accumulator->ids[$id])) {
//} return false;
}
}
// we purposely avoid using regex, hopefully this is faster // we purposely avoid using regex, hopefully this is faster
if (ctype_alpha($id)) { if (ctype_alpha($id)) {
$result = true; $result = true;
} else { } else {
if (!ctype_alpha(@$id[0])) return false; if (!ctype_alpha(@$id[0])) {
$trim = trim( // primitive style of regexps, I suppose return false;
}
// primitive style of regexps, I suppose
$trim = trim(
$id, $id,
'A..Za..z0..9:-._' 'A..Za..z0..9:-._'
); );
@ -56,15 +91,15 @@ class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
return false; return false;
} }
if (/*!$this->ref && */$result) $id_accumulator->add($id); if (!$this->selector && $result) {
$id_accumulator->add($id);
}
// if no change was made to the ID, return the result // if no change was made to the ID, return the result
// else, return the new id if stripping whitespace made it // else, return the new id if stripping whitespace made it
// valid, or return false. // valid, or return false.
return $result ? $id : false; return $result ? $id : false;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -10,32 +10,47 @@
class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
{ {
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = trim($string); $string = trim($string);
if ($string === '') return false; if ($string === '') {
return false;
}
$parent_result = parent::validate($string, $config, $context); $parent_result = parent::validate($string, $config, $context);
if ($parent_result !== false) return $parent_result; if ($parent_result !== false) {
return $parent_result;
}
$length = strlen($string); $length = strlen($string);
$last_char = $string[$length - 1]; $last_char = $string[$length - 1];
if ($last_char !== '%') return false; if ($last_char !== '%') {
return false;
}
$points = substr($string, 0, $length - 1); $points = substr($string, 0, $length - 1);
if (!is_numeric($points)) return false; if (!is_numeric($points)) {
return false;
$points = (int) $points;
if ($points < 0) return '0%';
if ($points > 100) return '100%';
return ((string) $points) . '%';
} }
$points = (int)$points;
if ($points < 0) {
return '0%';
}
if ($points > 100) {
return '100%';
}
return ((string)$points) . '%';
}
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -9,26 +9,44 @@
class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
{ {
/** Name config attribute to pull. */ /**
* Name config attribute to pull.
* @type string
*/
protected $name; protected $name;
public function __construct($name) { /**
* @param string $name
*/
public function __construct($name)
{
$configLookup = array( $configLookup = array(
'rel' => 'AllowedRel', 'rel' => 'AllowedRel',
'rev' => 'AllowedRev' 'rev' => 'AllowedRev'
); );
if (!isset($configLookup[$name])) { if (!isset($configLookup[$name])) {
trigger_error('Unrecognized attribute name for link '. trigger_error(
'relationship.', E_USER_ERROR); 'Unrecognized attribute name for link ' .
'relationship.',
E_USER_ERROR
);
return; return;
} }
$this->name = $configLookup[$name]; $this->name = $configLookup[$name];
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$allowed = $config->get('Attr.' . $this->name); $allowed = $config->get('Attr.' . $this->name);
if (empty($allowed)) return false; if (empty($allowed)) {
return false;
}
$string = $this->parseCDATA($string); $string = $this->parseCDATA($string);
$parts = explode(' ', $string); $parts = explode(' ', $string);
@ -37,17 +55,18 @@ class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
$ret_lookup = array(); $ret_lookup = array();
foreach ($parts as $part) { foreach ($parts as $part) {
$part = strtolower(trim($part)); $part = strtolower(trim($part));
if (!isset($allowed[$part])) continue; if (!isset($allowed[$part])) {
continue;
}
$ret_lookup[$part] = true; $ret_lookup[$part] = true;
} }
if (empty($ret_lookup)) return false; if (empty($ret_lookup)) {
$string = implode(' ', array_keys($ret_lookup)); return false;
}
return $string; $string = implode(' ', array_keys($ret_lookup));
return $string;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -9,33 +9,52 @@
class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
{ {
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = trim($string); $string = trim($string);
if ($string === '') return false; if ($string === '') {
return false;
}
$parent_result = parent::validate($string, $config, $context); $parent_result = parent::validate($string, $config, $context);
if ($parent_result !== false) return $parent_result; if ($parent_result !== false) {
return $parent_result;
}
$length = strlen($string); $length = strlen($string);
$last_char = $string[$length - 1]; $last_char = $string[$length - 1];
if ($last_char !== '*') return false; if ($last_char !== '*') {
return false;
}
$int = substr($string, 0, $length - 1); $int = substr($string, 0, $length - 1);
if ($int == '') return '*'; if ($int == '') {
if (!is_numeric($int)) return false; return '*';
}
$int = (int) $int; if (!is_numeric($int)) {
return false;
if ($int < 0) return false;
if ($int == 0) return '0';
if ($int == 1) return '*';
return ((string) $int) . '*';
} }
$int = (int)$int;
if ($int < 0) {
return false;
}
if ($int == 0) {
return '0';
}
if ($int == 1) {
return '*';
}
return ((string)$int) . '*';
}
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,24 +6,38 @@
class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
{ {
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = trim($string); $string = trim($string);
// early abort: '' and '0' (strings that convert to false) are invalid // early abort: '' and '0' (strings that convert to false) are invalid
if (!$string) return false; if (!$string) {
return false;
}
$tokens = $this->split($string, $config, $context); $tokens = $this->split($string, $config, $context);
$tokens = $this->filter($tokens, $config, $context); $tokens = $this->filter($tokens, $config, $context);
if (empty($tokens)) return false; if (empty($tokens)) {
return false;
}
return implode(' ', $tokens); return implode(' ', $tokens);
} }
/** /**
* Splits a space separated list of tokens into its constituent parts. * Splits a space separated list of tokens into its constituent parts.
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/ */
protected function split($string, $config, $context) { protected function split($string, $config, $context)
{
// OPTIMIZABLE! // OPTIMIZABLE!
// do the preg_match, capture all subpatterns for reformulation // do the preg_match, capture all subpatterns for reformulation
@ -31,8 +45,8 @@ class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
// escaping because I don't know how to do that with regexps // escaping because I don't know how to do that with regexps
// and plus it would complicate optimization efforts (you never // and plus it would complicate optimization efforts (you never
// see that anyway). // see that anyway).
$pattern = '/(?:(?<=\s)|\A)'. // look behind for space or string start $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start
'((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)'. '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' .
'(?:(?=\s)|\z)/'; // look ahead for space or string end '(?:(?=\s)|\z)/'; // look ahead for space or string end
preg_match_all($pattern, $string, $matches); preg_match_all($pattern, $string, $matches);
return $matches[1]; return $matches[1];
@ -42,11 +56,15 @@ class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
* Template method for removing certain tokens based on arbitrary criteria. * Template method for removing certain tokens based on arbitrary criteria.
* @note If we wanted to be really functional, we'd do an array_filter * @note If we wanted to be really functional, we'd do an array_filter
* with a callback. But... we're not. * with a callback. But... we're not.
* @param array $tokens
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/ */
protected function filter($tokens, $config, $context) { protected function filter($tokens, $config, $context)
{
return $tokens; return $tokens;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,43 +6,71 @@
class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
{ {
/**
* @type int
*/
protected $max; protected $max;
public function __construct($max = null) { /**
* @param int $max
*/
public function __construct($max = null)
{
$this->max = $max; $this->max = $max;
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = trim($string); $string = trim($string);
if ($string === '0') return $string; if ($string === '0') {
if ($string === '') return false; return $string;
}
if ($string === '') {
return false;
}
$length = strlen($string); $length = strlen($string);
if (substr($string, $length - 2) == 'px') { if (substr($string, $length - 2) == 'px') {
$string = substr($string, 0, $length - 2); $string = substr($string, 0, $length - 2);
} }
if (!is_numeric($string)) return false; if (!is_numeric($string)) {
$int = (int) $string; return false;
}
$int = (int)$string;
if ($int < 0) return '0'; if ($int < 0) {
return '0';
}
// upper-bound value, extremely high values can // upper-bound value, extremely high values can
// crash operating systems, see <http://ha.ckers.org/imagecrash.html> // crash operating systems, see <http://ha.ckers.org/imagecrash.html>
// WARNING, above link WILL crash you if you're using Windows // WARNING, above link WILL crash you if you're using Windows
if ($this->max !== null && $int > $this->max) return (string) $this->max; if ($this->max !== null && $int > $this->max) {
return (string)$this->max;
return (string) $int; }
return (string)$int;
} }
public function make($string) { /**
if ($string === '') $max = null; * @param string $string
else $max = (int) $string; * @return HTMLPurifier_AttrDef
*/
public function make($string)
{
if ($string === '') {
$max = null;
} else {
$max = (int)$string;
}
$class = get_class($this); $class = get_class($this);
return new $class($max); return new $class($max);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -11,17 +11,20 @@ class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
{ {
/** /**
* Bool indicating whether or not negative values are allowed * Whether or not negative values are allowed.
* @type bool
*/ */
protected $negative = true; protected $negative = true;
/** /**
* Bool indicating whether or not zero is allowed * Whether or not zero is allowed.
* @type bool
*/ */
protected $zero = true; protected $zero = true;
/** /**
* Bool indicating whether or not positive values are allowed * Whether or not positive values are allowed.
* @type bool
*/ */
protected $positive = true; protected $positive = true;
@ -30,44 +33,59 @@ class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
* @param $zero Bool indicating whether or not zero is allowed * @param $zero Bool indicating whether or not zero is allowed
* @param $positive Bool indicating whether or not positive values are allowed * @param $positive Bool indicating whether or not positive values are allowed
*/ */
public function __construct( public function __construct($negative = true, $zero = true, $positive = true)
$negative = true, $zero = true, $positive = true {
) {
$this->negative = $negative; $this->negative = $negative;
$this->zero = $zero; $this->zero = $zero;
$this->positive = $positive; $this->positive = $positive;
} }
public function validate($integer, $config, $context) { /**
* @param string $integer
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($integer, $config, $context)
{
$integer = $this->parseCDATA($integer); $integer = $this->parseCDATA($integer);
if ($integer === '') return false; if ($integer === '') {
return false;
}
// we could possibly simply typecast it to integer, but there are // we could possibly simply typecast it to integer, but there are
// certain fringe cases that must not return an integer. // certain fringe cases that must not return an integer.
// clip leading sign // clip leading sign
if ( $this->negative && $integer[0] === '-' ) { if ($this->negative && $integer[0] === '-') {
$digits = substr($integer, 1); $digits = substr($integer, 1);
if ($digits === '0') $integer = '0'; // rm minus sign for zero if ($digits === '0') {
} elseif( $this->positive && $integer[0] === '+' ) { $integer = '0';
} // rm minus sign for zero
} elseif ($this->positive && $integer[0] === '+') {
$digits = $integer = substr($integer, 1); // rm unnecessary plus $digits = $integer = substr($integer, 1); // rm unnecessary plus
} else { } else {
$digits = $integer; $digits = $integer;
} }
// test if it's numeric // test if it's numeric
if (!ctype_digit($digits)) return false; if (!ctype_digit($digits)) {
return false;
// perform scope tests
if (!$this->zero && $integer == 0) return false;
if (!$this->positive && $integer > 0) return false;
if (!$this->negative && $integer < 0) return false;
return $integer;
} }
// perform scope tests
if (!$this->zero && $integer == 0) {
return false;
}
if (!$this->positive && $integer > 0) {
return false;
}
if (!$this->negative && $integer < 0) {
return false;
}
return $integer;
}
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,15 +7,25 @@
class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
{ {
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$string = trim($string); $string = trim($string);
if (!$string) return false; if (!$string) {
return false;
}
$subtags = explode('-', $string); $subtags = explode('-', $string);
$num_subtags = count($subtags); $num_subtags = count($subtags);
if ($num_subtags == 0) return false; // sanity check if ($num_subtags == 0) { // sanity check
return false;
}
// process primary subtag : $subtags[0] // process primary subtag : $subtags[0]
$length = strlen($subtags[0]); $length = strlen($subtags[0]);
@ -23,15 +33,15 @@ class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
case 0: case 0:
return false; return false;
case 1: case 1:
if (! ($subtags[0] == 'x' || $subtags[0] == 'i') ) { if (!($subtags[0] == 'x' || $subtags[0] == 'i')) {
return false; return false;
} }
break; break;
case 2: case 2:
case 3: case 3:
if (! ctype_alpha($subtags[0]) ) { if (!ctype_alpha($subtags[0])) {
return false; return false;
} elseif (! ctype_lower($subtags[0]) ) { } elseif (!ctype_lower($subtags[0])) {
$subtags[0] = strtolower($subtags[0]); $subtags[0] = strtolower($subtags[0]);
} }
break; break;
@ -40,17 +50,23 @@ class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
} }
$new_string = $subtags[0]; $new_string = $subtags[0];
if ($num_subtags == 1) return $new_string; if ($num_subtags == 1) {
return $new_string;
}
// process second subtag : $subtags[1] // process second subtag : $subtags[1]
$length = strlen($subtags[1]); $length = strlen($subtags[1]);
if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) { if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
return $new_string; return $new_string;
} }
if (!ctype_lower($subtags[1])) $subtags[1] = strtolower($subtags[1]); if (!ctype_lower($subtags[1])) {
$subtags[1] = strtolower($subtags[1]);
}
$new_string .= '-' . $subtags[1]; $new_string .= '-' . $subtags[1];
if ($num_subtags == 2) return $new_string; if ($num_subtags == 2) {
return $new_string;
}
// process all other subtags, index 2 and up // process all other subtags, index 2 and up
for ($i = 2; $i < $num_subtags; $i++) { for ($i = 2; $i < $num_subtags; $i++) {
@ -63,11 +79,8 @@ class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
} }
$new_string .= '-' . $subtags[$i]; $new_string .= '-' . $subtags[$i];
} }
return $new_string; return $new_string;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,21 +6,41 @@
class HTMLPurifier_AttrDef_Switch class HTMLPurifier_AttrDef_Switch
{ {
/**
* @type string
*/
protected $tag; protected $tag;
protected $withTag, $withoutTag;
/**
* @type HTMLPurifier_AttrDef
*/
protected $withTag;
/**
* @type HTMLPurifier_AttrDef
*/
protected $withoutTag;
/** /**
* @param string $tag Tag name to switch upon * @param string $tag Tag name to switch upon
* @param HTMLPurifier_AttrDef $with_tag Call if token matches tag * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
* @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
*/ */
public function __construct($tag, $with_tag, $without_tag) { public function __construct($tag, $with_tag, $without_tag)
{
$this->tag = $tag; $this->tag = $tag;
$this->withTag = $with_tag; $this->withTag = $with_tag;
$this->withoutTag = $without_tag; $this->withoutTag = $without_tag;
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$token = $context->get('CurrentToken', true); $token = $context->get('CurrentToken', true);
if (!$token || $token->name !== $this->tag) { if (!$token || $token->name !== $this->tag) {
return $this->withoutTag->validate($string, $config, $context); return $this->withoutTag->validate($string, $config, $context);
@ -28,7 +48,6 @@ class HTMLPurifier_AttrDef_Switch
return $this->withTag->validate($string, $config, $context); return $this->withTag->validate($string, $config, $context);
} }
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,10 +6,16 @@
class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
{ {
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
return $this->parseCDATA($string); return $this->parseCDATA($string);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,31 +7,54 @@
class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
{ {
/**
* @type HTMLPurifier_URIParser
*/
protected $parser; protected $parser;
/**
* @type bool
*/
protected $embedsResource; protected $embedsResource;
/** /**
* @param $embeds_resource_resource Does the URI here result in an extra HTTP request? * @param bool $embeds_resource Does the URI here result in an extra HTTP request?
*/ */
public function __construct($embeds_resource = false) { public function __construct($embeds_resource = false)
{
$this->parser = new HTMLPurifier_URIParser(); $this->parser = new HTMLPurifier_URIParser();
$this->embedsResource = (bool) $embeds_resource; $this->embedsResource = (bool)$embeds_resource;
} }
public function make($string) { /**
$embeds = (bool) $string; * @param string $string
* @return HTMLPurifier_AttrDef_URI
*/
public function make($string)
{
$embeds = ($string === 'embedded');
return new HTMLPurifier_AttrDef_URI($embeds); return new HTMLPurifier_AttrDef_URI($embeds);
} }
public function validate($uri, $config, $context) { /**
* @param string $uri
if ($config->get('URI.Disable')) return false; * @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($uri, $config, $context)
{
if ($config->get('URI.Disable')) {
return false;
}
$uri = $this->parseCDATA($uri); $uri = $this->parseCDATA($uri);
// parse the URI // parse the URI
$uri = $this->parser->parse($uri); $uri = $this->parser->parse($uri);
if ($uri === false) return false; if ($uri === false) {
return false;
}
// add embedded flag to context for validators // add embedded flag to context for validators
$context->register('EmbeddedURI', $this->embedsResource); $context->register('EmbeddedURI', $this->embedsResource);
@ -41,23 +64,35 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
// generic validation // generic validation
$result = $uri->validate($config, $context); $result = $uri->validate($config, $context);
if (!$result) break; if (!$result) {
break;
}
// chained filtering // chained filtering
$uri_def = $config->getDefinition('URI'); $uri_def = $config->getDefinition('URI');
$result = $uri_def->filter($uri, $config, $context); $result = $uri_def->filter($uri, $config, $context);
if (!$result) break; if (!$result) {
break;
}
// scheme-specific validation // scheme-specific validation
$scheme_obj = $uri->getSchemeObj($config, $context); $scheme_obj = $uri->getSchemeObj($config, $context);
if (!$scheme_obj) break; if (!$scheme_obj) {
if ($this->embedsResource && !$scheme_obj->browsable) break; break;
}
if ($this->embedsResource && !$scheme_obj->browsable) {
break;
}
$result = $scheme_obj->validate($uri, $config, $context); $result = $scheme_obj->validate($uri, $config, $context);
if (!$result) break; if (!$result) {
break;
}
// Post chained filtering // Post chained filtering
$result = $uri_def->postFilter($uri, $config, $context); $result = $uri_def->postFilter($uri, $config, $context);
if (!$result) break; if (!$result) {
break;
}
// survived gauntlet // survived gauntlet
$ok = true; $ok = true;
@ -65,13 +100,12 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
} while (false); } while (false);
$context->destroy('EmbeddedURI'); $context->destroy('EmbeddedURI');
if (!$ok) return false; if (!$ok) {
return false;
}
// back to string // back to string
return $uri->toString(); return $uri->toString();
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -5,8 +5,11 @@ abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
/** /**
* Unpacks a mailbox into its display-name and address * Unpacks a mailbox into its display-name and address
* @param string $string
* @return mixed
*/ */
function unpack($string) { public function unpack($string)
{
// needs to be implemented // needs to be implemented
} }

View File

@ -7,15 +7,23 @@
class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
{ {
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
// no support for named mailboxes i.e. "Bob <bob@example.com>" // no support for named mailboxes i.e. "Bob <bob@example.com>"
// that needs more percent encoding to be done // that needs more percent encoding to be done
if ($string == '') return false; if ($string == '') {
return false;
}
$string = trim($string); $string = trim($string);
$result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string); $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
return $result ? $string : false; return $result ? $string : false;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,56 +7,122 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
{ {
/** /**
* Instance of HTMLPurifier_AttrDef_URI_IPv4 sub-validator * IPv4 sub-validator.
* @type HTMLPurifier_AttrDef_URI_IPv4
*/ */
protected $ipv4; protected $ipv4;
/** /**
* Instance of HTMLPurifier_AttrDef_URI_IPv6 sub-validator * IPv6 sub-validator.
* @type HTMLPurifier_AttrDef_URI_IPv6
*/ */
protected $ipv6; protected $ipv6;
public function __construct() { public function __construct()
{
$this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4(); $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
$this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6(); $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
} }
public function validate($string, $config, $context) { /**
* @param string $string
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($string, $config, $context)
{
$length = strlen($string); $length = strlen($string);
if ($string === '') return ''; // empty hostname is OK; it's usually semantically equivalent:
if ($length > 1 && $string[0] === '[' && $string[$length-1] === ']') { // the default host as defined by a URI scheme is used:
//
// If the URI scheme defines a default for host, then that
// default applies when the host subcomponent is undefined
// or when the registered name is empty (zero length).
if ($string === '') {
return '';
}
if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') {
//IPv6 //IPv6
$ip = substr($string, 1, $length - 2); $ip = substr($string, 1, $length - 2);
$valid = $this->ipv6->validate($ip, $config, $context); $valid = $this->ipv6->validate($ip, $config, $context);
if ($valid === false) return false; if ($valid === false) {
return '['. $valid . ']'; return false;
}
return '[' . $valid . ']';
} }
// need to do checks on unusual encodings too // need to do checks on unusual encodings too
$ipv4 = $this->ipv4->validate($string, $config, $context); $ipv4 = $this->ipv4->validate($string, $config, $context);
if ($ipv4 !== false) return $ipv4; if ($ipv4 !== false) {
return $ipv4;
}
// A regular domain name. // A regular domain name.
// This breaks I18N domain names, but we don't have proper IRI support, // This doesn't match I18N domain names, but we don't have proper IRI support,
// so force users to insert Punycode. If there's complaining we'll // so force users to insert Punycode.
// try to fix things into an international friendly form.
// There is not a good sense in which underscores should be
// allowed, since it's technically not! (And if you go as
// far to allow everything as specified by the DNS spec...
// well, that's literally everything, modulo some space limits
// for the components and the overall name (which, by the way,
// we are NOT checking!). So we (arbitrarily) decide this:
// let's allow underscores wherever we would have allowed
// hyphens, if they are enabled. This is a pretty good match
// for browser behavior, for example, a large number of browsers
// cannot handle foo_.example.com, but foo_bar.example.com is
// fairly well supported.
$underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
// The productions describing this are: // The productions describing this are:
$a = '[a-z]'; // alpha $a = '[a-z]'; // alpha
$an = '[a-z0-9]'; // alphanum $an = '[a-z0-9]'; // alphanum
$and = '[a-z0-9-]'; // alphanum | "-" $and = "[a-z0-9-$underscore]"; // alphanum | "-"
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
$domainlabel = "$an($and*$an)?"; $domainlabel = "$an($and*$an)?";
// toplabel = alpha | alpha *( alphanum | "-" ) alphanum // toplabel = alpha | alpha *( alphanum | "-" ) alphanum
$toplabel = "$a($and*$an)?"; $toplabel = "$a($and*$an)?";
// hostname = *( domainlabel "." ) toplabel [ "." ] // hostname = *( domainlabel "." ) toplabel [ "." ]
$match = preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string); if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
if (!$match) return false;
return $string; return $string;
} }
// If we have Net_IDNA2 support, we can support IRIs by
// punycoding them. (This is the most portable thing to do,
// since otherwise we have to assume browsers support
if ($config->get('Core.EnableIDNA')) {
$idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
// we need to encode each period separately
$parts = explode('.', $string);
try {
$new_parts = array();
foreach ($parts as $part) {
$encodable = false;
for ($i = 0, $c = strlen($part); $i < $c; $i++) {
if (ord($part[$i]) > 0x7a) {
$encodable = true;
break;
}
}
if (!$encodable) {
$new_parts[] = $part;
} else {
$new_parts[] = $idna->encode($part);
}
}
$string = implode('.', $new_parts);
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
} catch (Exception $e) {
// XXX error reporting
}
}
return false;
}
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -8,32 +8,38 @@ class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
{ {
/** /**
* IPv4 regex, protected so that IPv6 can reuse it * IPv4 regex, protected so that IPv6 can reuse it.
* @type string
*/ */
protected $ip4; protected $ip4;
public function validate($aIP, $config, $context) { /**
* @param string $aIP
if (!$this->ip4) $this->_loadRegex(); * @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) * @return bool|string
*/
public function validate($aIP, $config, $context)
{ {
return $aIP; if (!$this->ip4) {
$this->_loadRegex();
} }
if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) {
return $aIP;
}
return false; return false;
} }
/** /**
* Lazy load function to prevent regex from being stuffed in * Lazy load function to prevent regex from being stuffed in
* cache. * cache.
*/ */
protected function _loadRegex() { protected function _loadRegex()
{
$oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255 $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
$this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})"; $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -9,9 +9,17 @@
class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4 class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
{ {
public function validate($aIP, $config, $context) { /**
* @param string $aIP
if (!$this->ip4) $this->_loadRegex(); * @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool|string
*/
public function validate($aIP, $config, $context)
{
if (!$this->ip4) {
$this->_loadRegex();
}
$original = $aIP; $original = $aIP;
@ -20,23 +28,18 @@ class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
$pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128 $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128
// prefix check // prefix check
if (strpos($aIP, '/') !== false) if (strpos($aIP, '/') !== false) {
{ if (preg_match('#' . $pre . '$#s', $aIP, $find)) {
if (preg_match('#' . $pre . '$#s', $aIP, $find)) $aIP = substr($aIP, 0, 0 - strlen($find[0]));
{
$aIP = substr($aIP, 0, 0-strlen($find[0]));
unset($find); unset($find);
} } else {
else
{
return false; return false;
} }
} }
// IPv4-compatiblity check // IPv4-compatiblity check
if (preg_match('#(?<=:'.')' . $this->ip4 . '$#s', $aIP, $find)) if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) {
{ $aIP = substr($aIP, 0, 0 - strlen($find[0]));
$aIP = substr($aIP, 0, 0-strlen($find[0]));
$ip = explode('.', $find[0]); $ip = explode('.', $find[0]);
$ip = array_map('dechex', $ip); $ip = array_map('dechex', $ip);
$aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3]; $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
@ -46,54 +49,41 @@ class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
// compression check // compression check
$aIP = explode('::', $aIP); $aIP = explode('::', $aIP);
$c = count($aIP); $c = count($aIP);
if ($c > 2) if ($c > 2) {
{
return false; return false;
} } elseif ($c == 2) {
elseif ($c == 2)
{
list($first, $second) = $aIP; list($first, $second) = $aIP;
$first = explode(':', $first); $first = explode(':', $first);
$second = explode(':', $second); $second = explode(':', $second);
if (count($first) + count($second) > 8) if (count($first) + count($second) > 8) {
{
return false; return false;
} }
while(count($first) < 8) while (count($first) < 8) {
{
array_push($first, '0'); array_push($first, '0');
} }
array_splice($first, 8 - count($second), 8, $second); array_splice($first, 8 - count($second), 8, $second);
$aIP = $first; $aIP = $first;
unset($first,$second); unset($first, $second);
} } else {
else
{
$aIP = explode(':', $aIP[0]); $aIP = explode(':', $aIP[0]);
} }
$c = count($aIP); $c = count($aIP);
if ($c != 8) if ($c != 8) {
{
return false; return false;
} }
// All the pieces should be 16-bit hex strings. Are they? // All the pieces should be 16-bit hex strings. Are they?
foreach ($aIP as $piece) foreach ($aIP as $piece) {
{ if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) {
if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece)))
{
return false; return false;
} }
} }
return $original; return $original;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -20,37 +20,41 @@ abstract class HTMLPurifier_AttrTransform
/** /**
* Abstract: makes changes to the attributes dependent on multiple values. * Abstract: makes changes to the attributes dependent on multiple values.
* *
* @param $attr Assoc array of attributes, usually from * @param array $attr Assoc array of attributes, usually from
* HTMLPurifier_Token_Tag::$attr * HTMLPurifier_Token_Tag::$attr
* @param $config Mandatory HTMLPurifier_Config object. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
* @param $context Mandatory HTMLPurifier_Context object * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
* @returns Processed attribute array. * @return array Processed attribute array.
*/ */
abstract public function transform($attr, $config, $context); abstract public function transform($attr, $config, $context);
/** /**
* Prepends CSS properties to the style attribute, creating the * Prepends CSS properties to the style attribute, creating the
* attribute if it doesn't exist. * attribute if it doesn't exist.
* @param $attr Attribute array to process (passed by reference) * @param array &$attr Attribute array to process (passed by reference)
* @param $css CSS to prepend * @param string $css CSS to prepend
*/ */
public function prependCSS(&$attr, $css) { public function prependCSS(&$attr, $css)
{
$attr['style'] = isset($attr['style']) ? $attr['style'] : ''; $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
$attr['style'] = $css . $attr['style']; $attr['style'] = $css . $attr['style'];
} }
/** /**
* Retrieves and removes an attribute * Retrieves and removes an attribute
* @param $attr Attribute array to process (passed by reference) * @param array &$attr Attribute array to process (passed by reference)
* @param $key Key of attribute to confiscate * @param mixed $key Key of attribute to confiscate
* @return mixed
*/ */
public function confiscateAttr(&$attr, $key) { public function confiscateAttr(&$attr, $key)
if (!isset($attr[$key])) return null; {
if (!isset($attr[$key])) {
return null;
}
$value = $attr[$key]; $value = $attr[$key];
unset($attr[$key]); unset($attr[$key]);
return $value; return $value;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -3,21 +3,26 @@
/** /**
* Pre-transform that changes proprietary background attribute to CSS. * Pre-transform that changes proprietary background attribute to CSS.
*/ */
class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform { class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) { /**
* @param array $attr
if (!isset($attr['background'])) return $attr; * @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr['background'])) {
return $attr;
}
$background = $this->confiscateAttr($attr, 'background'); $background = $this->confiscateAttr($attr, 'background');
// some validation should happen here // some validation should happen here
$this->prependCSS($attr, "background-image:url($background);"); $this->prependCSS($attr, "background-image:url($background);");
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -8,12 +8,20 @@
class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
{ {
public function transform($attr, $config, $context) { /**
if (isset($attr['dir'])) return $attr; * @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (isset($attr['dir'])) {
return $attr;
}
$attr['dir'] = $config->get('Attr.DefaultTextDir'); $attr['dir'] = $config->get('Attr.DefaultTextDir');
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -3,21 +3,26 @@
/** /**
* Pre-transform that changes deprecated bgcolor attribute to CSS. * Pre-transform that changes deprecated bgcolor attribute to CSS.
*/ */
class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform { class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) { /**
* @param array $attr
if (!isset($attr['bgcolor'])) return $attr; * @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr['bgcolor'])) {
return $attr;
}
$bgcolor = $this->confiscateAttr($attr, 'bgcolor'); $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
// some validation should happen here // some validation should happen here
$this->prependCSS($attr, "background-color:$bgcolor;"); $this->prependCSS($attr, "background-color:$bgcolor;");
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -3,34 +3,45 @@
/** /**
* Pre-transform that changes converts a boolean attribute to fixed CSS * Pre-transform that changes converts a boolean attribute to fixed CSS
*/ */
class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform { class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform
{
/** /**
* Name of boolean attribute that is trigger * Name of boolean attribute that is trigger.
* @type string
*/ */
protected $attr; protected $attr;
/** /**
* CSS declarations to add to style, needs trailing semicolon * CSS declarations to add to style, needs trailing semicolon.
* @type string
*/ */
protected $css; protected $css;
/** /**
* @param $attr string attribute name to convert from * @param string $attr attribute name to convert from
* @param $css string CSS declarations to add to style (needs semicolon) * @param string $css CSS declarations to add to style (needs semicolon)
*/ */
public function __construct($attr, $css) { public function __construct($attr, $css)
{
$this->attr = $attr; $this->attr = $attr;
$this->css = $css; $this->css = $css;
} }
public function transform($attr, $config, $context) { /**
if (!isset($attr[$this->attr])) return $attr; * @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr[$this->attr])) {
return $attr;
}
unset($attr[$this->attr]); unset($attr[$this->attr]);
$this->prependCSS($attr, $this->css); $this->prependCSS($attr, $this->css);
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -3,16 +3,24 @@
/** /**
* Pre-transform that changes deprecated border attribute to CSS. * Pre-transform that changes deprecated border attribute to CSS.
*/ */
class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform { class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) { /**
if (!isset($attr['border'])) return $attr; * @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr['border'])) {
return $attr;
}
$border_width = $this->confiscateAttr($attr, 'border'); $border_width = $this->confiscateAttr($attr, 'border');
// some validation should happen here // some validation should happen here
$this->prependCSS($attr, "border:{$border_width}px solid;"); $this->prependCSS($attr, "border:{$border_width}px solid;");
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -4,55 +4,65 @@
* Generic pre-transform that converts an attribute with a fixed number of * Generic pre-transform that converts an attribute with a fixed number of
* values (enumerated) to CSS. * values (enumerated) to CSS.
*/ */
class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform { class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform
{
/** /**
* Name of attribute to transform from * Name of attribute to transform from.
* @type string
*/ */
protected $attr; protected $attr;
/** /**
* Lookup array of attribute values to CSS * Lookup array of attribute values to CSS.
* @type array
*/ */
protected $enumToCSS = array(); protected $enumToCSS = array();
/** /**
* Case sensitivity of the matching * Case sensitivity of the matching.
* @type bool
* @warning Currently can only be guaranteed to work with ASCII * @warning Currently can only be guaranteed to work with ASCII
* values. * values.
*/ */
protected $caseSensitive = false; protected $caseSensitive = false;
/** /**
* @param $attr String attribute name to transform from * @param string $attr Attribute name to transform from
* @param $enumToCSS Lookup array of attribute values to CSS * @param array $enum_to_css Lookup array of attribute values to CSS
* @param $case_sensitive Boolean case sensitivity indicator, default false * @param bool $case_sensitive Case sensitivity indicator, default false
*/ */
public function __construct($attr, $enum_to_css, $case_sensitive = false) { public function __construct($attr, $enum_to_css, $case_sensitive = false)
{
$this->attr = $attr; $this->attr = $attr;
$this->enumToCSS = $enum_to_css; $this->enumToCSS = $enum_to_css;
$this->caseSensitive = (bool) $case_sensitive; $this->caseSensitive = (bool)$case_sensitive;
} }
public function transform($attr, $config, $context) { /**
* @param array $attr
if (!isset($attr[$this->attr])) return $attr; * @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr[$this->attr])) {
return $attr;
}
$value = trim($attr[$this->attr]); $value = trim($attr[$this->attr]);
unset($attr[$this->attr]); unset($attr[$this->attr]);
if (!$this->caseSensitive) $value = strtolower($value); if (!$this->caseSensitive) {
$value = strtolower($value);
}
if (!isset($this->enumToCSS[$value])) { if (!isset($this->enumToCSS[$value])) {
return $attr; return $attr;
} }
$this->prependCSS($attr, $this->enumToCSS[$value]); $this->prependCSS($attr, $this->enumToCSS[$value]);
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -11,11 +11,19 @@
class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
{ {
public function transform($attr, $config, $context) { /**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
$src = true; $src = true;
if (!isset($attr['src'])) { if (!isset($attr['src'])) {
if ($config->get('Core.RemoveInvalidImg')) return $attr; if ($config->get('Core.RemoveInvalidImg')) {
return $attr;
}
$attr['src'] = $config->get('Attr.DefaultInvalidImage'); $attr['src'] = $config->get('Attr.DefaultInvalidImage');
$src = false; $src = false;
} }
@ -25,7 +33,7 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
$alt = $config->get('Attr.DefaultImageAlt'); $alt = $config->get('Attr.DefaultImageAlt');
if ($alt === null) { if ($alt === null) {
// truncate if the alt is too long // truncate if the alt is too long
$attr['alt'] = substr(basename($attr['src']),0,40); $attr['alt'] = substr(basename($attr['src']), 0, 40);
} else { } else {
$attr['alt'] = $alt; $attr['alt'] = $alt;
} }
@ -33,11 +41,8 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
$attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt'); $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt');
} }
} }
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -3,42 +3,59 @@
/** /**
* Pre-transform that changes deprecated hspace and vspace attributes to CSS * Pre-transform that changes deprecated hspace and vspace attributes to CSS
*/ */
class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform { class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform
{
/**
* @type string
*/
protected $attr; protected $attr;
/**
* @type array
*/
protected $css = array( protected $css = array(
'hspace' => array('left', 'right'), 'hspace' => array('left', 'right'),
'vspace' => array('top', 'bottom') 'vspace' => array('top', 'bottom')
); );
public function __construct($attr) { /**
* @param string $attr
*/
public function __construct($attr)
{
$this->attr = $attr; $this->attr = $attr;
if (!isset($this->css[$attr])) { if (!isset($this->css[$attr])) {
trigger_error(htmlspecialchars($attr) . ' is not valid space attribute'); trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
} }
} }
public function transform($attr, $config, $context) { /**
* @param array $attr
if (!isset($attr[$this->attr])) return $attr; * @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr[$this->attr])) {
return $attr;
}
$width = $this->confiscateAttr($attr, $this->attr); $width = $this->confiscateAttr($attr, $this->attr);
// some validation could happen here // some validation could happen here
if (!isset($this->css[$this->attr])) return $attr; if (!isset($this->css[$this->attr])) {
return $attr;
}
$style = ''; $style = '';
foreach ($this->css[$this->attr] as $suffix) { foreach ($this->css[$this->attr] as $suffix) {
$property = "margin-$suffix"; $property = "margin-$suffix";
$style .= "$property:{$width}px;"; $style .= "$property:{$width}px;";
} }
$this->prependCSS($attr, $style); $this->prependCSS($attr, $style);
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -4,17 +4,31 @@
* Performs miscellaneous cross attribute validation and filtering for * Performs miscellaneous cross attribute validation and filtering for
* input elements. This is meant to be a post-transform. * input elements. This is meant to be a post-transform.
*/ */
class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform { class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform
{
/**
* @type HTMLPurifier_AttrDef_HTML_Pixels
*/
protected $pixels; protected $pixels;
public function __construct() { public function __construct()
{
$this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels(); $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
} }
public function transform($attr, $config, $context) { /**
if (!isset($attr['type'])) $t = 'text'; * @param array $attr
else $t = strtolower($attr['type']); * @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr['type'])) {
$t = 'text';
} else {
$t = strtolower($attr['type']);
}
if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') { if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
unset($attr['checked']); unset($attr['checked']);
} }
@ -23,8 +37,11 @@ class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform {
} }
if (isset($attr['size']) && $t !== 'text' && $t !== 'password') { if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
$result = $this->pixels->validate($attr['size'], $config, $context); $result = $this->pixels->validate($attr['size'], $config, $context);
if ($result === false) unset($attr['size']); if ($result === false) {
else $attr['size'] = $result; unset($attr['size']);
} else {
$attr['size'] = $result;
}
} }
if (isset($attr['src']) && $t !== 'image') { if (isset($attr['src']) && $t !== 'image') {
unset($attr['src']); unset($attr['src']);
@ -34,7 +51,6 @@ class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform {
} }
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -8,8 +8,14 @@
class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
{ {
public function transform($attr, $config, $context) { /**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
$lang = isset($attr['lang']) ? $attr['lang'] : false; $lang = isset($attr['lang']) ? $attr['lang'] : false;
$xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false; $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
@ -18,11 +24,8 @@ class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
} elseif ($xml_lang !== false) { } elseif ($xml_lang !== false) {
$attr['lang'] = $xml_lang; $attr['lang'] = $xml_lang;
} }
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,22 +6,40 @@
class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
{ {
/**
* @type string
*/
protected $name; protected $name;
/**
* @type string
*/
protected $cssName; protected $cssName;
public function __construct($name, $css_name = null) { public function __construct($name, $css_name = null)
{
$this->name = $name; $this->name = $name;
$this->cssName = $css_name ? $css_name : $name; $this->cssName = $css_name ? $css_name : $name;
} }
public function transform($attr, $config, $context) { /**
if (!isset($attr[$this->name])) return $attr; * @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr[$this->name])) {
return $attr;
}
$length = $this->confiscateAttr($attr, $this->name); $length = $this->confiscateAttr($attr, $this->name);
if(ctype_digit($length)) $length .= 'px'; if (ctype_digit($length)) {
$length .= 'px';
}
$this->prependCSS($attr, $this->cssName . ":$length;"); $this->prependCSS($attr, $this->cssName . ":$length;");
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,16 +6,28 @@
class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
{ {
public function transform($attr, $config, $context) { /**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
// Abort early if we're using relaxed definition of name // Abort early if we're using relaxed definition of name
if ($config->get('HTML.Attr.Name.UseCDATA')) return $attr; if ($config->get('HTML.Attr.Name.UseCDATA')) {
if (!isset($attr['name'])) return $attr; return $attr;
}
if (!isset($attr['name'])) {
return $attr;
}
$id = $this->confiscateAttr($attr, 'name'); $id = $this->confiscateAttr($attr, 'name');
if ( isset($attr['id'])) return $attr; if (isset($attr['id'])) {
return $attr;
}
$attr['id'] = $id; $attr['id'] = $id;
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -8,20 +8,34 @@
class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
{ {
public function __construct() { public function __construct()
{
$this->idDef = new HTMLPurifier_AttrDef_HTML_ID(); $this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
} }
public function transform($attr, $config, $context) { /**
if (!isset($attr['name'])) return $attr; * @param array $attr
$name = $attr['name']; * @param HTMLPurifier_Config $config
if (isset($attr['id']) && $attr['id'] === $name) return $attr; * @param HTMLPurifier_Context $context
$result = $this->idDef->validate($name, $config, $context); * @return array
if ($result === false) unset($attr['name']); */
else $attr['name'] = $result; public function transform($attr, $config, $context)
{
if (!isset($attr['name'])) {
return $attr;
}
$name = $attr['name'];
if (isset($attr['id']) && $attr['id'] === $name) {
return $attr;
}
$result = $this->idDef->validate($name, $config, $context);
if ($result === false) {
unset($attr['name']);
} else {
$attr['name'] = $result;
}
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -0,0 +1,52 @@
<?php
// must be called POST validation
/**
* Adds rel="nofollow" to all outbound links. This transform is
* only attached if Attr.Nofollow is TRUE.
*/
class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
{
/**
* @type HTMLPurifier_URIParser
*/
private $parser;
public function __construct()
{
$this->parser = new HTMLPurifier_URIParser();
}
/**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr['href'])) {
return $attr;
}
// XXX Kind of inefficient
$url = $this->parser->parse($attr['href']);
$scheme = $url->getSchemeObj($config, $context);
if ($scheme->browsable && !$url->isLocal($config, $context)) {
if (isset($attr['rel'])) {
$rels = explode(' ', $attr['rel']);
if (!in_array('nofollow', $rels)) {
$rels[] = 'nofollow';
}
$attr['rel'] = implode(' ', $rels);
} else {
$attr['rel'] = 'nofollow';
}
}
return $attr;
}
}
// vim: et sw=4 sts=4

View File

@ -2,9 +2,19 @@
class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
{ {
/**
* @type string
*/
public $name = "SafeEmbed"; public $name = "SafeEmbed";
public function transform($attr, $config, $context) { /**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
$attr['allowscriptaccess'] = 'never'; $attr['allowscriptaccess'] = 'never';
$attr['allownetworking'] = 'internal'; $attr['allownetworking'] = 'internal';
$attr['type'] = 'application/x-shockwave-flash'; $attr['type'] = 'application/x-shockwave-flash';

View File

@ -5,10 +5,22 @@
*/ */
class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
{ {
/**
* @type string
*/
public $name = "SafeObject"; public $name = "SafeObject";
function transform($attr, $config, $context) { /**
if (!isset($attr['type'])) $attr['type'] = 'application/x-shockwave-flash'; * @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr['type'])) {
$attr['type'] = 'application/x-shockwave-flash';
}
return $attr; return $attr;
} }
} }

View File

@ -14,14 +14,30 @@
*/ */
class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
{ {
/**
* @type string
*/
public $name = "SafeParam"; public $name = "SafeParam";
/**
* @type HTMLPurifier_AttrDef_URI
*/
private $uri; private $uri;
public function __construct() { public function __construct()
{
$this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
$this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
} }
public function transform($attr, $config, $context) { /**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
// If we add support for other objects, we'll need to alter the // If we add support for other objects, we'll need to alter the
// transforms. // transforms.
switch ($attr['name']) { switch ($attr['name']) {
@ -33,8 +49,15 @@ class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
case 'allowNetworking': case 'allowNetworking':
$attr['value'] = 'internal'; $attr['value'] = 'internal';
break; break;
case 'allowFullScreen':
if ($config->get('HTML.FlashAllowFullScreen')) {
$attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false';
} else {
$attr['value'] = 'false';
}
break;
case 'wmode': case 'wmode':
$attr['value'] = 'window'; $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
break; break;
case 'movie': case 'movie':
case 'src': case 'src':

View File

@ -5,7 +5,14 @@
*/ */
class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
{ {
public function transform($attr, $config, $context) { /**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr['type'])) { if (!isset($attr['type'])) {
$attr['type'] = 'text/javascript'; $attr['type'] = 'text/javascript';
} }

View File

@ -0,0 +1,45 @@
<?php
// must be called POST validation
/**
* Adds target="blank" to all outbound links. This transform is
* only attached if Attr.TargetBlank is TRUE. This works regardless
* of whether or not Attr.AllowedFrameTargets
*/
class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
{
/**
* @type HTMLPurifier_URIParser
*/
private $parser;
public function __construct()
{
$this->parser = new HTMLPurifier_URIParser();
}
/**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (!isset($attr['href'])) {
return $attr;
}
// XXX Kind of inefficient
$url = $this->parser->parse($attr['href']);
$scheme = $url->getSchemeObj($config, $context);
if ($scheme->browsable && !$url->isBenign($config, $context)) {
$attr['target'] = '_blank';
}
return $attr;
}
}
// vim: et sw=4 sts=4

View File

@ -5,14 +5,23 @@
*/ */
class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
{ {
/**
public function transform($attr, $config, $context) { * @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
// Calculated from Firefox // Calculated from Firefox
if (!isset($attr['cols'])) $attr['cols'] = '22'; if (!isset($attr['cols'])) {
if (!isset($attr['rows'])) $attr['rows'] = '3'; $attr['cols'] = '22';
}
if (!isset($attr['rows'])) {
$attr['rows'] = '3';
}
return $attr; return $attr;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,7 +6,8 @@
class HTMLPurifier_AttrTypes class HTMLPurifier_AttrTypes
{ {
/** /**
* Lookup array of attribute string identifiers to concrete implementations * Lookup array of attribute string identifiers to concrete implementations.
* @type HTMLPurifier_AttrDef[]
*/ */
protected $info = array(); protected $info = array();
@ -14,7 +15,15 @@ class HTMLPurifier_AttrTypes
* Constructs the info array, supplying default implementations for attribute * Constructs the info array, supplying default implementations for attribute
* types. * types.
*/ */
public function __construct() { public function __construct()
{
// XXX This is kind of poor, since we don't actually /clone/
// instances; instead, we use the supplied make() attribute. So,
// the underlying class must know how to deal with arguments.
// With the old implementation of Enum, that ignored its
// arguments when handling a make dispatch, the IAlign
// definition wouldn't work.
// pseudo-types, must be instantiated via shorthand // pseudo-types, must be instantiated via shorthand
$this->info['Enum'] = new HTMLPurifier_AttrDef_Enum(); $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum();
$this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool();
@ -29,6 +38,9 @@ class HTMLPurifier_AttrTypes
$this->info['URI'] = new HTMLPurifier_AttrDef_URI(); $this->info['URI'] = new HTMLPurifier_AttrDef_URI();
$this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
$this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color(); $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
$this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right');
$this->info['LAlign'] = self::makeEnum('top,bottom,left,right');
$this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();
// unimplemented aliases // unimplemented aliases
$this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
@ -44,32 +56,39 @@ class HTMLPurifier_AttrTypes
$this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
} }
private static function makeEnum($in)
{
return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
}
/** /**
* Retrieves a type * Retrieves a type
* @param $type String type name * @param string $type String type name
* @return Object AttrDef for type * @return HTMLPurifier_AttrDef Object AttrDef for type
*/ */
public function get($type) { public function get($type)
{
// determine if there is any extra info tacked on // determine if there is any extra info tacked on
if (strpos($type, '#') !== false) list($type, $string) = explode('#', $type, 2); if (strpos($type, '#') !== false) {
else $string = ''; list($type, $string) = explode('#', $type, 2);
} else {
$string = '';
}
if (!isset($this->info[$type])) { if (!isset($this->info[$type])) {
trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR); trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
return; return;
} }
return $this->info[$type]->make($string); return $this->info[$type]->make($string);
} }
/** /**
* Sets a new implementation for a type * Sets a new implementation for a type
* @param $type String type name * @param string $type String type name
* @param $impl Object AttrDef for type * @param HTMLPurifier_AttrDef $impl Object AttrDef for type
*/ */
public function set($type, $impl) { public function set($type, $impl)
{
$this->info[$type] = $impl; $this->info[$type] = $impl;
} }
} }

View File

@ -9,17 +9,14 @@ class HTMLPurifier_AttrValidator
{ {
/** /**
* Validates the attributes of a token, returning a modified token * Validates the attributes of a token, mutating it as necessary.
* that has valid tokens * that has valid tokens
* @param $token Reference to token to validate. We require a reference * @param HTMLPurifier_Token $token Token to validate.
* because the operation this class performs on the token are * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
* not atomic, so the context CurrentToken to be updated * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
* throughout
* @param $config Instance of HTMLPurifier_Config
* @param $context Instance of HTMLPurifier_Context
*/ */
public function validateToken(&$token, &$config, $context) { public function validateToken($token, $config, $context)
{
$definition = $config->getHTMLDefinition(); $definition = $config->getHTMLDefinition();
$e =& $context->get('ErrorCollector', true); $e =& $context->get('ErrorCollector', true);
@ -32,12 +29,15 @@ class HTMLPurifier_AttrValidator
// initialize CurrentToken if necessary // initialize CurrentToken if necessary
$current_token =& $context->get('CurrentToken', true); $current_token =& $context->get('CurrentToken', true);
if (!$current_token) $context->register('CurrentToken', $token); if (!$current_token) {
$context->register('CurrentToken', $token);
}
if ( if (!$token instanceof HTMLPurifier_Token_Start &&
!$token instanceof HTMLPurifier_Token_Start &&
!$token instanceof HTMLPurifier_Token_Empty !$token instanceof HTMLPurifier_Token_Empty
) return $token; ) {
return;
}
// create alias to global definition array, see also $defs // create alias to global definition array, see also $defs
// DEFINITION CALL // DEFINITION CALL
@ -51,7 +51,9 @@ class HTMLPurifier_AttrValidator
foreach ($definition->info_attr_transform_pre as $transform) { foreach ($definition->info_attr_transform_pre as $transform) {
$attr = $transform->transform($o = $attr, $config, $context); $attr = $transform->transform($o = $attr, $config, $context);
if ($e) { if ($e) {
if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); if ($attr != $o) {
$e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
}
} }
} }
@ -60,7 +62,9 @@ class HTMLPurifier_AttrValidator
foreach ($definition->info[$token->name]->attr_transform_pre as $transform) { foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
$attr = $transform->transform($o = $attr, $config, $context); $attr = $transform->transform($o = $attr, $config, $context);
if ($e) { if ($e) {
if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); if ($attr != $o) {
$e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
}
} }
} }
@ -77,7 +81,7 @@ class HTMLPurifier_AttrValidator
foreach ($attr as $attr_key => $value) { foreach ($attr as $attr_key => $value) {
// call the definition // call the definition
if ( isset($defs[$attr_key]) ) { if (isset($defs[$attr_key])) {
// there is a local definition defined // there is a local definition defined
if ($defs[$attr_key] === false) { if ($defs[$attr_key] === false) {
// We've explicitly been told not to allow this element. // We've explicitly been told not to allow this element.
@ -89,14 +93,18 @@ class HTMLPurifier_AttrValidator
} else { } else {
// validate according to the element's definition // validate according to the element's definition
$result = $defs[$attr_key]->validate( $result = $defs[$attr_key]->validate(
$value, $config, $context $value,
$config,
$context
); );
} }
} elseif ( isset($d_defs[$attr_key]) ) { } elseif (isset($d_defs[$attr_key])) {
// there is a global definition defined, validate according // there is a global definition defined, validate according
// to the global definition // to the global definition
$result = $d_defs[$attr_key]->validate( $result = $d_defs[$attr_key]->validate(
$value, $config, $context $value,
$config,
$context
); );
} else { } else {
// system never heard of the attribute? DELETE! // system never heard of the attribute? DELETE!
@ -107,7 +115,9 @@ class HTMLPurifier_AttrValidator
if ($result === false || $result === null) { if ($result === false || $result === null) {
// this is a generic error message that should replaced // this is a generic error message that should replaced
// with more specific ones when possible // with more specific ones when possible
if ($e) $e->send(E_ERROR, 'AttrValidator: Attribute removed'); if ($e) {
$e->send(E_ERROR, 'AttrValidator: Attribute removed');
}
// remove the attribute // remove the attribute
unset($attr[$attr_key]); unset($attr[$attr_key]);
@ -137,7 +147,9 @@ class HTMLPurifier_AttrValidator
foreach ($definition->info_attr_transform_post as $transform) { foreach ($definition->info_attr_transform_post as $transform) {
$attr = $transform->transform($o = $attr, $config, $context); $attr = $transform->transform($o = $attr, $config, $context);
if ($e) { if ($e) {
if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); if ($attr != $o) {
$e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
}
} }
} }
@ -145,14 +157,18 @@ class HTMLPurifier_AttrValidator
foreach ($definition->info[$token->name]->attr_transform_post as $transform) { foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
$attr = $transform->transform($o = $attr, $config, $context); $attr = $transform->transform($o = $attr, $config, $context);
if ($e) { if ($e) {
if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); if ($attr != $o) {
$e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
}
} }
} }
$token->attr = $attr; $token->attr = $attr;
// destroy CurrentToken if we made it ourselves // destroy CurrentToken if we made it ourselves
if (!$current_token) $context->destroy('CurrentToken'); if (!$current_token) {
$context->destroy('CurrentToken');
}
} }

View File

@ -32,20 +32,34 @@ class HTMLPurifier_Bootstrap
/** /**
* Autoload function for HTML Purifier * Autoload function for HTML Purifier
* @param $class Class to load * @param string $class Class to load
* @return bool
*/ */
public static function autoload($class) { public static function autoload($class)
{
$file = HTMLPurifier_Bootstrap::getPath($class); $file = HTMLPurifier_Bootstrap::getPath($class);
if (!$file) return false; if (!$file) {
require HTMLPURIFIER_PREFIX . '/' . $file; return false;
}
// Technically speaking, it should be ok and more efficient to
// just do 'require', but Antonio Parraga reports that with
// Zend extensions such as Zend debugger and APC, this invariant
// may be broken. Since we have efficient alternatives, pay
// the cost here and avoid the bug.
require_once HTMLPURIFIER_PREFIX . '/' . $file;
return true; return true;
} }
/** /**
* Returns the path for a specific class. * Returns the path for a specific class.
* @param string $class Class path to get
* @return string
*/ */
public static function getPath($class) { public static function getPath($class)
if (strncmp('HTMLPurifier', $class, 12) !== 0) return false; {
if (strncmp('HTMLPurifier', $class, 12) !== 0) {
return false;
}
// Custom implementations // Custom implementations
if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
$code = str_replace('_', '-', substr($class, 22)); $code = str_replace('_', '-', substr($class, 22));
@ -53,46 +67,58 @@ class HTMLPurifier_Bootstrap
} else { } else {
$file = str_replace('_', '/', $class) . '.php'; $file = str_replace('_', '/', $class) . '.php';
} }
if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) return false; if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
return false;
}
return $file; return $file;
} }
/** /**
* "Pre-registers" our autoloader on the SPL stack. * "Pre-registers" our autoloader on the SPL stack.
*/ */
public static function registerAutoload() { public static function registerAutoload()
{
$autoload = array('HTMLPurifier_Bootstrap', 'autoload'); $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
if ( ($funcs = spl_autoload_functions()) === false ) { if (($funcs = spl_autoload_functions()) === false) {
spl_autoload_register($autoload); spl_autoload_register($autoload);
} elseif (function_exists('spl_autoload_unregister')) { } elseif (function_exists('spl_autoload_unregister')) {
if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
// prepend flag exists, no need for shenanigans
spl_autoload_register($autoload, true, true);
} else {
$buggy = version_compare(PHP_VERSION, '5.2.11', '<');
$compat = version_compare(PHP_VERSION, '5.1.2', '<=') && $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
version_compare(PHP_VERSION, '5.1.0', '>='); version_compare(PHP_VERSION, '5.1.0', '>=');
foreach ($funcs as $func) { foreach ($funcs as $func) {
if (is_array($func)) { if ($buggy && is_array($func)) {
// :TRICKY: There are some compatibility issues and some // :TRICKY: There are some compatibility issues and some
// places where we need to error out // places where we need to error out
$reflector = new ReflectionMethod($func[0], $func[1]); $reflector = new ReflectionMethod($func[0], $func[1]);
if (!$reflector->isStatic()) { if (!$reflector->isStatic()) {
throw new Exception(' throw new Exception(
HTML Purifier autoloader registrar is not compatible 'HTML Purifier autoloader registrar is not compatible
with non-static object methods due to PHP Bug #44144; with non-static object methods due to PHP Bug #44144;
Please do not use HTMLPurifier.autoload.php (or any Please do not use HTMLPurifier.autoload.php (or any
file that includes this file); instead, place the code: file that includes this file); instead, place the code:
spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\')) spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
after your own autoloaders. after your own autoloaders.'
'); );
} }
// Suprisingly, spl_autoload_register supports the // Suprisingly, spl_autoload_register supports the
// Class::staticMethod callback format, although call_user_func doesn't // Class::staticMethod callback format, although call_user_func doesn't
if ($compat) $func = implode('::', $func); if ($compat) {
$func = implode('::', $func);
}
} }
spl_autoload_unregister($func); spl_autoload_unregister($func);
} }
spl_autoload_register($autoload); spl_autoload_register($autoload);
foreach ($funcs as $func) spl_autoload_register($func); foreach ($funcs as $func) {
spl_autoload_register($func);
}
}
} }
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -11,35 +11,59 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
/** /**
* Assoc array of attribute name to definition object. * Assoc array of attribute name to definition object.
* @type HTMLPurifier_AttrDef[]
*/ */
public $info = array(); public $info = array();
/** /**
* Constructs the info array. The meat of this class. * Constructs the info array. The meat of this class.
* @param HTMLPurifier_Config $config
*/ */
protected function doSetup($config) { protected function doSetup($config)
{
$this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
array('left', 'right', 'center', 'justify'), false); array('left', 'right', 'center', 'justify'),
false
);
$border_style = $border_style =
$this->info['border-bottom-style'] = $this->info['border-bottom-style'] =
$this->info['border-right-style'] = $this->info['border-right-style'] =
$this->info['border-left-style'] = $this->info['border-left-style'] =
$this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double', array(
'groove', 'ridge', 'inset', 'outset'), false); 'none',
'hidden',
'dotted',
'dashed',
'solid',
'double',
'groove',
'ridge',
'inset',
'outset'
),
false
);
$this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
$this->info['clear'] = new HTMLPurifier_AttrDef_Enum( $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
array('none', 'left', 'right', 'both'), false); array('none', 'left', 'right', 'both'),
false
);
$this->info['float'] = new HTMLPurifier_AttrDef_Enum( $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
array('none', 'left', 'right'), false); array('none', 'left', 'right'),
false
);
$this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
array('normal', 'italic', 'oblique'), false); array('normal', 'italic', 'oblique'),
false
);
$this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
array('normal', 'small-caps'), false); array('normal', 'small-caps'),
false
);
$uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
array( array(
@ -49,16 +73,31 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
); );
$this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
array('inside', 'outside'), false); array('inside', 'outside'),
false
);
$this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
array('disc', 'circle', 'square', 'decimal', 'lower-roman', array(
'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false); 'disc',
'circle',
'square',
'decimal',
'lower-roman',
'upper-roman',
'lower-alpha',
'upper-alpha',
'none'
),
false
);
$this->info['list-style-image'] = $uri_or_none; $this->info['list-style-image'] = $uri_or_none;
$this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
$this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
array('capitalize', 'uppercase', 'lowercase', 'none'), false); array('capitalize', 'uppercase', 'lowercase', 'none'),
false
);
$this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['background-image'] = $uri_or_none; $this->info['background-image'] = $uri_or_none;
@ -75,10 +114,12 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$this->info['border-bottom-color'] = $this->info['border-bottom-color'] =
$this->info['border-left-color'] = $this->info['border-left-color'] =
$this->info['border-right-color'] = $this->info['border-right-color'] =
$this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_Enum(array('transparent')), new HTMLPurifier_AttrDef_Enum(array('transparent')),
new HTMLPurifier_AttrDef_CSS_Color() new HTMLPurifier_AttrDef_CSS_Color()
)); )
);
$this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
@ -88,47 +129,69 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$this->info['border-top-width'] = $this->info['border-top-width'] =
$this->info['border-bottom-width'] = $this->info['border-bottom-width'] =
$this->info['border-left-width'] = $this->info['border-left-width'] =
$this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
)); )
);
$this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
$this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_Enum(array('normal')), new HTMLPurifier_AttrDef_Enum(array('normal')),
new HTMLPurifier_AttrDef_CSS_Length() new HTMLPurifier_AttrDef_CSS_Length()
)); )
);
$this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_Enum(array('normal')), new HTMLPurifier_AttrDef_Enum(array('normal')),
new HTMLPurifier_AttrDef_CSS_Length() new HTMLPurifier_AttrDef_CSS_Length()
)); )
);
$this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small', array(
'small', 'medium', 'large', 'x-large', 'xx-large', new HTMLPurifier_AttrDef_Enum(
'larger', 'smaller')), array(
'xx-small',
'x-small',
'small',
'medium',
'large',
'x-large',
'xx-large',
'larger',
'smaller'
)
),
new HTMLPurifier_AttrDef_CSS_Percentage(), new HTMLPurifier_AttrDef_CSS_Percentage(),
new HTMLPurifier_AttrDef_CSS_Length() new HTMLPurifier_AttrDef_CSS_Length()
)); )
);
$this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_Enum(array('normal')), new HTMLPurifier_AttrDef_Enum(array('normal')),
new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
new HTMLPurifier_AttrDef_CSS_Length('0'), new HTMLPurifier_AttrDef_CSS_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true) new HTMLPurifier_AttrDef_CSS_Percentage(true)
)); )
);
$margin = $margin =
$this->info['margin-top'] = $this->info['margin-top'] =
$this->info['margin-bottom'] = $this->info['margin-bottom'] =
$this->info['margin-left'] = $this->info['margin-left'] =
$this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_CSS_Length(), new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage(), new HTMLPurifier_AttrDef_CSS_Percentage(),
new HTMLPurifier_AttrDef_Enum(array('auto')) new HTMLPurifier_AttrDef_Enum(array('auto'))
)); )
);
$this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
@ -137,35 +200,44 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$this->info['padding-top'] = $this->info['padding-top'] =
$this->info['padding-bottom'] = $this->info['padding-bottom'] =
$this->info['padding-left'] = $this->info['padding-left'] =
$this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_CSS_Length('0'), new HTMLPurifier_AttrDef_CSS_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true) new HTMLPurifier_AttrDef_CSS_Percentage(true)
)); )
);
$this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
$this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_CSS_Length(), new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage() new HTMLPurifier_AttrDef_CSS_Percentage()
)); )
);
$trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array( $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_CSS_Length('0'), new HTMLPurifier_AttrDef_CSS_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true), new HTMLPurifier_AttrDef_CSS_Percentage(true),
new HTMLPurifier_AttrDef_Enum(array('auto')) new HTMLPurifier_AttrDef_Enum(array('auto'))
)); )
);
$max = $config->get('CSS.MaxImgLength'); $max = $config->get('CSS.MaxImgLength');
$this->info['width'] = $this->info['width'] =
$this->info['height'] = $this->info['height'] =
$max === null ? $max === null ?
$trusted_wh : $trusted_wh :
new HTMLPurifier_AttrDef_Switch('img', new HTMLPurifier_AttrDef_Switch(
'img',
// For img tags: // For img tags:
new HTMLPurifier_AttrDef_CSS_Composite(array( new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_CSS_Length('0', $max), new HTMLPurifier_AttrDef_CSS_Length('0', $max),
new HTMLPurifier_AttrDef_Enum(array('auto')) new HTMLPurifier_AttrDef_Enum(array('auto'))
)), )
),
// For everyone else: // For everyone else:
$trusted_wh $trusted_wh
); );
@ -176,8 +248,23 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
// this could use specialized code // this could use specialized code
$this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300', array(
'400', '500', '600', '700', '800', '900'), false); 'normal',
'bold',
'bolder',
'lighter',
'100',
'200',
'300',
'400',
'500',
'600',
'700',
'800',
'900'
),
false
);
// MUST be called after other font properties, as it references // MUST be called after other font properties, as it references
// a CSSDefinition object // a CSSDefinition object
@ -190,26 +277,44 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$this->info['border-left'] = $this->info['border-left'] =
$this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
$this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array( $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
'collapse', 'separate')); array('collapse', 'separate')
);
$this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array( $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
'top', 'bottom')); array('top', 'bottom')
);
$this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array( $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
'auto', 'fixed')); array('auto', 'fixed')
);
$this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array( $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super', array(
'top', 'text-top', 'middle', 'bottom', 'text-bottom')), new HTMLPurifier_AttrDef_Enum(
array(
'baseline',
'sub',
'super',
'top',
'text-top',
'middle',
'bottom',
'text-bottom'
)
),
new HTMLPurifier_AttrDef_CSS_Length(), new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage() new HTMLPurifier_AttrDef_CSS_Percentage()
)); )
);
$this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
// partial support // These CSS properties don't work on many browsers, but we live
$this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap')); // in THE FUTURE!
$this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
);
if ($config->get('CSS.Proprietary')) { if ($config->get('CSS.Proprietary')) {
$this->doSetupProprietary($config); $this->doSetupProprietary($config);
@ -219,6 +324,10 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$this->doSetupTricky($config); $this->doSetupTricky($config);
} }
if ($config->get('CSS.Trusted')) {
$this->doSetupTrusted($config);
}
$allow_important = $config->get('CSS.AllowImportant'); $allow_important = $config->get('CSS.AllowImportant');
// wrap all attr-defs with decorator that handles !important // wrap all attr-defs with decorator that handles !important
foreach ($this->info as $k => $v) { foreach ($this->info as $k => $v) {
@ -228,7 +337,11 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$this->setupConfigStuff($config); $this->setupConfigStuff($config);
} }
protected function doSetupProprietary($config) { /**
* @param HTMLPurifier_Config $config
*/
protected function doSetupProprietary($config)
{
// Internet Explorer only scrollbar colors // Internet Explorer only scrollbar colors
$this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
@ -245,47 +358,116 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
// only opacity, for now // only opacity, for now
$this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
// more CSS3
$this->info['page-break-after'] =
$this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
array(
'auto',
'always',
'avoid',
'left',
'right'
)
);
$this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
} }
protected function doSetupTricky($config) { /**
$this->info['display'] = new HTMLPurifier_AttrDef_Enum(array( * @param HTMLPurifier_Config $config
'inline', 'block', 'list-item', 'run-in', 'compact', */
'marker', 'table', 'inline-table', 'table-row-group', protected function doSetupTricky($config)
'table-header-group', 'table-footer-group', 'table-row', {
'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none' $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
)); array(
$this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array( 'inline',
'visible', 'hidden', 'collapse' 'block',
)); 'list-item',
'run-in',
'compact',
'marker',
'table',
'inline-block',
'inline-table',
'table-row-group',
'table-header-group',
'table-footer-group',
'table-row',
'table-column-group',
'table-column',
'table-cell',
'table-caption',
'none'
)
);
$this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
array('visible', 'hidden', 'collapse')
);
$this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
} }
/**
* @param HTMLPurifier_Config $config
*/
protected function doSetupTrusted($config)
{
$this->info['position'] = new HTMLPurifier_AttrDef_Enum(
array('static', 'relative', 'absolute', 'fixed')
);
$this->info['top'] =
$this->info['left'] =
$this->info['right'] =
$this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage(),
new HTMLPurifier_AttrDef_Enum(array('auto')),
)
);
$this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_Integer(),
new HTMLPurifier_AttrDef_Enum(array('auto')),
)
);
}
/** /**
* Performs extra config-based processing. Based off of * Performs extra config-based processing. Based off of
* HTMLPurifier_HTMLDefinition. * HTMLPurifier_HTMLDefinition.
* @param HTMLPurifier_Config $config
* @todo Refactor duplicate elements into common class (probably using * @todo Refactor duplicate elements into common class (probably using
* composition, not inheritance). * composition, not inheritance).
*/ */
protected function setupConfigStuff($config) { protected function setupConfigStuff($config)
{
// setup allowed elements // setup allowed elements
$support = "(for information on implementing this, see the ". $support = "(for information on implementing this, see the " .
"support forums) "; "support forums) ";
$allowed_attributes = $config->get('CSS.AllowedProperties'); $allowed_properties = $config->get('CSS.AllowedProperties');
if ($allowed_attributes !== null) { if ($allowed_properties !== null) {
foreach ($this->info as $name => $d) { foreach ($this->info as $name => $d) {
if(!isset($allowed_attributes[$name])) unset($this->info[$name]); if (!isset($allowed_properties[$name])) {
unset($allowed_attributes[$name]); unset($this->info[$name]);
}
unset($allowed_properties[$name]);
} }
// emit errors // emit errors
foreach ($allowed_attributes as $name => $d) { foreach ($allowed_properties as $name => $d) {
// :TODO: Is this htmlspecialchars() call really necessary? // :TODO: Is this htmlspecialchars() call really necessary?
$name = htmlspecialchars($name); $name = htmlspecialchars($name);
trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
} }
} }
$forbidden_properties = $config->get('CSS.ForbiddenProperties');
if ($forbidden_properties !== null) {
foreach ($this->info as $name => $d) {
if (isset($forbidden_properties[$name])) {
unset($this->info[$name]);
}
}
}
} }
} }

View File

@ -1,48 +1,52 @@
<?php <?php
/** /**
* Defines allowed child nodes and validates tokens against it. * Defines allowed child nodes and validates nodes against it.
*/ */
abstract class HTMLPurifier_ChildDef abstract class HTMLPurifier_ChildDef
{ {
/** /**
* Type of child definition, usually right-most part of class name lowercase. * Type of child definition, usually right-most part of class name lowercase.
* Used occasionally in terms of context. * Used occasionally in terms of context.
* @type string
*/ */
public $type; public $type;
/** /**
* Bool that indicates whether or not an empty array of children is okay * Indicates whether or not an empty array of children is okay.
* *
* This is necessary for redundant checking when changes affecting * This is necessary for redundant checking when changes affecting
* a child node may cause a parent node to now be disallowed. * a child node may cause a parent node to now be disallowed.
* @type bool
*/ */
public $allow_empty; public $allow_empty;
/** /**
* Lookup array of all elements that this definition could possibly allow * Lookup array of all elements that this definition could possibly allow.
* @type array
*/ */
public $elements = array(); public $elements = array();
/** /**
* Get lookup of tag names that should not close this element automatically. * Get lookup of tag names that should not close this element automatically.
* All other elements will do so. * All other elements will do so.
* @param HTMLPurifier_Config $config HTMLPurifier_Config object
* @return array
*/ */
public function getAllowedElements($config) { public function getAllowedElements($config)
{
return $this->elements; return $this->elements;
} }
/** /**
* Validates nodes according to definition and returns modification. * Validates nodes according to definition and returns modification.
* *
* @param $tokens_of_children Array of HTMLPurifier_Token * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
* @param $config HTMLPurifier_Config object * @param HTMLPurifier_Config $config HTMLPurifier_Config object
* @param $context HTMLPurifier_Context object * @param HTMLPurifier_Context $context HTMLPurifier_Context object
* @return bool true to leave nodes as is * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
* @return bool false to remove parent node
* @return array of replacement child tokens
*/ */
abstract public function validateChildren($tokens_of_children, $config, $context); abstract public function validateChildren($children, $config, $context);
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -14,33 +14,52 @@ class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
/** /**
* Instance of the definition object to use when inline. Usually stricter. * Instance of the definition object to use when inline. Usually stricter.
* @type HTMLPurifier_ChildDef_Optional
*/ */
public $inline; public $inline;
/** /**
* Instance of the definition object to use when block. * Instance of the definition object to use when block.
* @type HTMLPurifier_ChildDef_Optional
*/ */
public $block; public $block;
/**
* @type string
*/
public $type = 'chameleon'; public $type = 'chameleon';
/** /**
* @param $inline List of elements to allow when inline. * @param array $inline List of elements to allow when inline.
* @param $block List of elements to allow when block. * @param array $block List of elements to allow when block.
*/ */
public function __construct($inline, $block) { public function __construct($inline, $block)
{
$this->inline = new HTMLPurifier_ChildDef_Optional($inline); $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
$this->block = new HTMLPurifier_ChildDef_Optional($block); $this->block = new HTMLPurifier_ChildDef_Optional($block);
$this->elements = $this->block->elements; $this->elements = $this->block->elements;
} }
public function validateChildren($tokens_of_children, $config, $context) { /**
* @param HTMLPurifier_Node[] $children
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool
*/
public function validateChildren($children, $config, $context)
{
if ($context->get('IsInline') === false) { if ($context->get('IsInline') === false) {
return $this->block->validateChildren( return $this->block->validateChildren(
$tokens_of_children, $config, $context); $children,
$config,
$context
);
} else { } else {
return $this->inline->validateChildren( return $this->inline->validateChildren(
$tokens_of_children, $config, $context); $children,
$config,
$context
);
} }
} }
} }

View File

@ -8,28 +8,42 @@
*/ */
class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
{ {
public $type = 'custom';
public $allow_empty = false;
/** /**
* Allowed child pattern as defined by the DTD * @type string
*/
public $type = 'custom';
/**
* @type bool
*/
public $allow_empty = false;
/**
* Allowed child pattern as defined by the DTD.
* @type string
*/ */
public $dtd_regex; public $dtd_regex;
/** /**
* PCRE regex derived from $dtd_regex * PCRE regex derived from $dtd_regex.
* @private * @type string
*/ */
private $_pcre_regex; private $_pcre_regex;
/** /**
* @param $dtd_regex Allowed child pattern from the DTD * @param $dtd_regex Allowed child pattern from the DTD
*/ */
public function __construct($dtd_regex) { public function __construct($dtd_regex)
{
$this->dtd_regex = $dtd_regex; $this->dtd_regex = $dtd_regex;
$this->_compileRegex(); $this->_compileRegex();
} }
/** /**
* Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex) * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
*/ */
protected function _compileRegex() { protected function _compileRegex()
{
$raw = str_replace(' ', '', $this->dtd_regex); $raw = str_replace(' ', '', $this->dtd_regex);
if ($raw{0} != '(') { if ($raw{0} != '(') {
$raw = "($raw)"; $raw = "($raw)";
@ -57,33 +71,31 @@ class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
$this->_pcre_regex = $reg; $this->_pcre_regex = $reg;
} }
public function validateChildren($tokens_of_children, $config, $context) {
/**
* @param HTMLPurifier_Node[] $children
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool
*/
public function validateChildren($children, $config, $context)
{
$list_of_children = ''; $list_of_children = '';
$nesting = 0; // depth into the nest $nesting = 0; // depth into the nest
foreach ($tokens_of_children as $token) { foreach ($children as $node) {
if (!empty($token->is_whitespace)) continue; if (!empty($node->is_whitespace)) {
continue;
$is_child = ($nesting == 0); // direct
if ($token instanceof HTMLPurifier_Token_Start) {
$nesting++;
} elseif ($token instanceof HTMLPurifier_Token_End) {
$nesting--;
}
if ($is_child) {
$list_of_children .= $token->name . ',';
} }
$list_of_children .= $node->name . ',';
} }
// add leading comma to deal with stray comma declarations // add leading comma to deal with stray comma declarations
$list_of_children = ',' . rtrim($list_of_children, ','); $list_of_children = ',' . rtrim($list_of_children, ',');
$okay = $okay =
preg_match( preg_match(
'/^,?'.$this->_pcre_regex.'$/', '/^,?' . $this->_pcre_regex . '$/',
$list_of_children $list_of_children
); );
return (bool)$okay;
return (bool) $okay;
} }
} }

View File

@ -9,10 +9,28 @@
*/ */
class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
{ {
/**
* @type bool
*/
public $allow_empty = true; public $allow_empty = true;
/**
* @type string
*/
public $type = 'empty'; public $type = 'empty';
public function __construct() {}
public function validateChildren($tokens_of_children, $config, $context) { public function __construct()
{
}
/**
* @param HTMLPurifier_Node[] $children
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function validateChildren($children, $config, $context)
{
return array(); return array();
} }
} }

View File

@ -0,0 +1,86 @@
<?php
/**
* Definition for list containers ul and ol.
*
* What does this do? The big thing is to handle ol/ul at the top
* level of list nodes, which should be handled specially by /folding/
* them into the previous list node. We generally shouldn't ever
* see other disallowed elements, because the autoclose behavior
* in MakeWellFormed handles it.
*/
class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
{
/**
* @type string
*/
public $type = 'list';
/**
* @type array
*/
// lying a little bit, so that we can handle ul and ol ourselves
// XXX: This whole business with 'wrap' is all a bit unsatisfactory
public $elements = array('li' => true, 'ul' => true, 'ol' => true);
/**
* @param array $children
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function validateChildren($children, $config, $context)
{
// Flag for subclasses
$this->whitespace = false;
// if there are no tokens, delete parent node
if (empty($children)) {
return false;
}
// the new set of children
$result = array();
// a little sanity check to make sure it's not ALL whitespace
$all_whitespace = true;
$current_li = false;
foreach ($children as $node) {
if (!empty($node->is_whitespace)) {
$result[] = $node;
continue;
}
$all_whitespace = false; // phew, we're not talking about whitespace
if ($node->name === 'li') {
// good
$current_li = $node;
$result[] = $node;
} else {
// we want to tuck this into the previous li
// Invariant: we expect the node to be ol/ul
// ToDo: Make this more robust in the case of not ol/ul
// by distinguishing between existing li and li created
// to handle non-list elements; non-list elements should
// not be appended to an existing li; only li created
// for non-list. This distinction is not currently made.
if ($current_li === false) {
$current_li = new HTMLPurifier_Node_Element('li');
$result[] = $current_li;
}
$current_li->children[] = $node;
$current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo
}
}
if (empty($result)) {
return false;
}
if ($all_whitespace) {
return false;
}
return $result;
}
}
// vim: et sw=4 sts=4

View File

@ -9,15 +9,34 @@
*/ */
class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
{ {
/**
* @type bool
*/
public $allow_empty = true; public $allow_empty = true;
/**
* @type string
*/
public $type = 'optional'; public $type = 'optional';
public function validateChildren($tokens_of_children, $config, $context) {
$result = parent::validateChildren($tokens_of_children, $config, $context); /**
// we assume that $tokens_of_children is not modified * @param array $children
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function validateChildren($children, $config, $context)
{
$result = parent::validateChildren($children, $config, $context);
// we assume that $children is not modified
if ($result === false) { if ($result === false) {
if (empty($tokens_of_children)) return true; if (empty($children)) {
elseif ($this->whitespace) return $tokens_of_children; return true;
else return array(); } elseif ($this->whitespace) {
return $children;
} else {
return array();
}
} }
return $result; return $result;
} }

View File

@ -7,17 +7,21 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
{ {
/** /**
* Lookup table of allowed elements. * Lookup table of allowed elements.
* @public * @type array
*/ */
public $elements = array(); public $elements = array();
/** /**
* Whether or not the last passed node was all whitespace. * Whether or not the last passed node was all whitespace.
* @type bool
*/ */
protected $whitespace = false; protected $whitespace = false;
/** /**
* @param $elements List of allowed element names (lowercase). * @param array|string $elements List of allowed element names (lowercase).
*/ */
public function __construct($elements) { public function __construct($elements)
{
if (is_string($elements)) { if (is_string($elements)) {
$elements = str_replace(' ', '', $elements); $elements = str_replace(' ', '', $elements);
$elements = explode('|', $elements); $elements = explode('|', $elements);
@ -27,29 +31,43 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
$elements = array_flip($elements); $elements = array_flip($elements);
foreach ($elements as $i => $x) { foreach ($elements as $i => $x) {
$elements[$i] = true; $elements[$i] = true;
if (empty($i)) unset($elements[$i]); // remove blank if (empty($i)) {
unset($elements[$i]);
} // remove blank
} }
} }
$this->elements = $elements; $this->elements = $elements;
} }
/**
* @type bool
*/
public $allow_empty = false; public $allow_empty = false;
/**
* @type string
*/
public $type = 'required'; public $type = 'required';
public function validateChildren($tokens_of_children, $config, $context) {
/**
* @param array $children
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function validateChildren($children, $config, $context)
{
// Flag for subclasses // Flag for subclasses
$this->whitespace = false; $this->whitespace = false;
// if there are no tokens, delete parent node // if there are no tokens, delete parent node
if (empty($tokens_of_children)) return false; if (empty($children)) {
return false;
}
// the new set of children // the new set of children
$result = array(); $result = array();
// current depth into the nest
$nesting = 0;
// whether or not we're deleting a node
$is_deleting = false;
// whether or not parsed character data is allowed // whether or not parsed character data is allowed
// this controls whether or not we silently drop a tag // this controls whether or not we silently drop a tag
// or generate escaped HTML from it // or generate escaped HTML from it
@ -58,58 +76,41 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
// a little sanity check to make sure it's not ALL whitespace // a little sanity check to make sure it's not ALL whitespace
$all_whitespace = true; $all_whitespace = true;
// some configuration $stack = array_reverse($children);
$escape_invalid_children = $config->get('Core.EscapeInvalidChildren'); while (!empty($stack)) {
$node = array_pop($stack);
// generator if (!empty($node->is_whitespace)) {
$gen = new HTMLPurifier_Generator($config, $context); $result[] = $node;
foreach ($tokens_of_children as $token) {
if (!empty($token->is_whitespace)) {
$result[] = $token;
continue; continue;
} }
$all_whitespace = false; // phew, we're not talking about whitespace $all_whitespace = false; // phew, we're not talking about whitespace
$is_child = ($nesting == 0); if (!isset($this->elements[$node->name])) {
// special case text
if ($token instanceof HTMLPurifier_Token_Start) { // XXX One of these ought to be redundant or something
$nesting++; if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
} elseif ($token instanceof HTMLPurifier_Token_End) { $result[] = $node;
$nesting--; continue;
} }
// spill the child contents in
if ($is_child) { // ToDo: Make configurable
$is_deleting = false; if ($node instanceof HTMLPurifier_Node_Element) {
if (!isset($this->elements[$token->name])) { for ($i = count($node->children) - 1; $i >= 0; $i--) {
$is_deleting = true; $stack[] = $node->children[$i];
if ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text) {
$result[] = $token;
} elseif ($pcdata_allowed && $escape_invalid_children) {
$result[] = new HTMLPurifier_Token_Text(
$gen->generateFromToken($token)
);
} }
continue; continue;
} }
continue;
} }
if (!$is_deleting || ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text)) { $result[] = $node;
$result[] = $token;
} elseif ($pcdata_allowed && $escape_invalid_children) {
$result[] =
new HTMLPurifier_Token_Text(
$gen->generateFromToken($token)
);
} else {
// drop silently
} }
if (empty($result)) {
return false;
} }
if (empty($result)) return false;
if ($all_whitespace) { if ($all_whitespace) {
$this->whitespace = true; $this->whitespace = true;
return false; return false;
} }
if ($tokens_of_children == $result) return true;
return $result; return $result;
} }
} }

View File

@ -5,75 +5,97 @@
*/ */
class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
{ {
/**
* @type array
*/
protected $real_elements; protected $real_elements;
/**
* @type array
*/
protected $fake_elements; protected $fake_elements;
/**
* @type bool
*/
public $allow_empty = true; public $allow_empty = true;
/**
* @type string
*/
public $type = 'strictblockquote'; public $type = 'strictblockquote';
/**
* @type bool
*/
protected $init = false; protected $init = false;
/** /**
* @param HTMLPurifier_Config $config
* @return array
* @note We don't want MakeWellFormed to auto-close inline elements since * @note We don't want MakeWellFormed to auto-close inline elements since
* they might be allowed. * they might be allowed.
*/ */
public function getAllowedElements($config) { public function getAllowedElements($config)
{
$this->init($config); $this->init($config);
return $this->fake_elements; return $this->fake_elements;
} }
public function validateChildren($tokens_of_children, $config, $context) { /**
* @param array $children
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function validateChildren($children, $config, $context)
{
$this->init($config); $this->init($config);
// trick the parent class into thinking it allows more // trick the parent class into thinking it allows more
$this->elements = $this->fake_elements; $this->elements = $this->fake_elements;
$result = parent::validateChildren($tokens_of_children, $config, $context); $result = parent::validateChildren($children, $config, $context);
$this->elements = $this->real_elements; $this->elements = $this->real_elements;
if ($result === false) return array(); if ($result === false) {
if ($result === true) $result = $tokens_of_children; return array();
}
if ($result === true) {
$result = $children;
}
$def = $config->getHTMLDefinition(); $def = $config->getHTMLDefinition();
$block_wrap_start = new HTMLPurifier_Token_Start($def->info_block_wrapper); $block_wrap_name = $def->info_block_wrapper;
$block_wrap_end = new HTMLPurifier_Token_End( $def->info_block_wrapper); $block_wrap = false;
$is_inline = false;
$depth = 0;
$ret = array(); $ret = array();
// assuming that there are no comment tokens foreach ($result as $node) {
foreach ($result as $i => $token) { if ($block_wrap === false) {
$token = $result[$i]; if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
// ifs are nested for readability ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
if (!$is_inline) { $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
if (!$depth) { $ret[] = $block_wrap;
if (
($token instanceof HTMLPurifier_Token_Text && !$token->is_whitespace) ||
(!$token instanceof HTMLPurifier_Token_Text && !isset($this->elements[$token->name]))
) {
$is_inline = true;
$ret[] = $block_wrap_start;
}
} }
} else { } else {
if (!$depth) { if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
// starting tokens have been inline text / empty $block_wrap = false;
if ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) {
if (isset($this->elements[$token->name])) {
// ended
$ret[] = $block_wrap_end;
$is_inline = false;
} }
} }
if ($block_wrap) {
$block_wrap->children[] = $node;
} else {
$ret[] = $node;
} }
} }
$ret[] = $token;
if ($token instanceof HTMLPurifier_Token_Start) $depth++;
if ($token instanceof HTMLPurifier_Token_End) $depth--;
}
if ($is_inline) $ret[] = $block_wrap_end;
return $ret; return $ret;
} }
private function init($config) { /**
* @param HTMLPurifier_Config $config
*/
private function init($config)
{
if (!$this->init) { if (!$this->init) {
$def = $config->getHTMLDefinition(); $def = $config->getHTMLDefinition();
// allow all inline elements // allow all inline elements

View File

@ -1,140 +1,222 @@
<?php <?php
/** /**
* Definition for tables * Definition for tables. The general idea is to extract out all of the
* essential bits, and then reconstruct it later.
*
* This is a bit confusing, because the DTDs and the W3C
* validators seem to disagree on the appropriate definition. The
* DTD claims:
*
* (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)
*
* But actually, the HTML4 spec then has this to say:
*
* The TBODY start tag is always required except when the table
* contains only one table body and no table head or foot sections.
* The TBODY end tag may always be safely omitted.
*
* So the DTD is kind of wrong. The validator is, unfortunately, kind
* of on crack.
*
* The definition changed again in XHTML1.1; and in my opinion, this
* formulation makes the most sense.
*
* caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))
*
* Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.
* If we encounter a thead, tfoot or tbody, we are placed in the former
* mode, and we *must* wrap any stray tr segments with a tbody. But if
* we don't run into any of them, just have tr tags is OK.
*/ */
class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
{ {
/**
* @type bool
*/
public $allow_empty = false; public $allow_empty = false;
public $type = 'table';
public $elements = array('tr' => true, 'tbody' => true, 'thead' => true,
'tfoot' => true, 'caption' => true, 'colgroup' => true, 'col' => true);
public function __construct() {}
public function validateChildren($tokens_of_children, $config, $context) {
if (empty($tokens_of_children)) return false;
// this ensures that the loop gets run one last time before closing /**
// up. It's a little bit of a hack, but it works! Just make sure you * @type string
// get rid of the token later. */
$tokens_of_children[] = false; public $type = 'table';
/**
* @type array
*/
public $elements = array(
'tr' => true,
'tbody' => true,
'thead' => true,
'tfoot' => true,
'caption' => true,
'colgroup' => true,
'col' => true
);
public function __construct()
{
}
/**
* @param array $children
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function validateChildren($children, $config, $context)
{
if (empty($children)) {
return false;
}
// only one of these elements is allowed in a table // only one of these elements is allowed in a table
$caption = false; $caption = false;
$thead = false; $thead = false;
$tfoot = false; $tfoot = false;
// whitespace
$initial_ws = array();
$after_caption_ws = array();
$after_thead_ws = array();
$after_tfoot_ws = array();
// as many of these as you want // as many of these as you want
$cols = array(); $cols = array();
$content = array(); $content = array();
$nesting = 0; // current depth so we can determine nodes $tbody_mode = false; // if true, then we need to wrap any stray
$is_collecting = false; // are we globbing together tokens to package // <tr>s with a <tbody>.
// into one of the collectors?
$collection = array(); // collected nodes
$tag_index = 0; // the first node might be whitespace,
// so this tells us where the start tag is
foreach ($tokens_of_children as $token) { $ws_accum =& $initial_ws;
$is_child = ($nesting == 0);
if ($token === false) { foreach ($children as $node) {
// terminating sequence started if ($node instanceof HTMLPurifier_Node_Comment) {
} elseif ($token instanceof HTMLPurifier_Token_Start) { $ws_accum[] = $node;
$nesting++; continue;
} elseif ($token instanceof HTMLPurifier_Token_End) {
$nesting--;
} }
switch ($node->name) {
// handle node collection
if ($is_collecting) {
if ($is_child) {
// okay, let's stash the tokens away
// first token tells us the type of the collection
switch ($collection[$tag_index]->name) {
case 'tr':
case 'tbody': case 'tbody':
$content[] = $collection; $tbody_mode = true;
// fall through
case 'tr':
$content[] = $node;
$ws_accum =& $content;
break; break;
case 'caption': case 'caption':
// there can only be one caption!
if ($caption !== false) break; if ($caption !== false) break;
$caption = $collection; $caption = $node;
$ws_accum =& $after_caption_ws;
break; break;
case 'thead': case 'thead':
case 'tfoot': $tbody_mode = true;
// access the appropriate variable, $thead or $tfoot // XXX This breaks rendering properties with
$var = $collection[$tag_index]->name; // Firefox, which never floats a <thead> to
if ($$var === false) { // the top. Ever. (Our scheme will float the
$$var = $collection; // first <thead> to the top.) So maybe
// <thead>s that are not first should be
// turned into <tbody>? Very tricky, indeed.
if ($thead === false) {
$thead = $node;
$ws_accum =& $after_thead_ws;
} else { } else {
// transmutate the first and less entries into // Oops, there's a second one! What
// tbody tags, and then put into content // should we do? Current behavior is to
$collection[$tag_index]->name = 'tbody'; // transmutate the first and last entries into
$collection[count($collection)-1]->name = 'tbody'; // tbody tags, and then put into content.
$content[] = $collection; // Maybe a better idea is to *attach
// it* to the existing thead or tfoot?
// We don't do this, because Firefox
// doesn't float an extra tfoot to the
// bottom like it does for the first one.
$node->name = 'tbody';
$content[] = $node;
$ws_accum =& $content;
}
break;
case 'tfoot':
// see above for some aveats
$tbody_mode = true;
if ($tfoot === false) {
$tfoot = $node;
$ws_accum =& $after_tfoot_ws;
} else {
$node->name = 'tbody';
$content[] = $node;
$ws_accum =& $content;
} }
break; break;
case 'colgroup': case 'colgroup':
$cols[] = $collection; case 'col':
$cols[] = $node;
$ws_accum =& $cols;
break;
case '#PCDATA':
// How is whitespace handled? We treat is as sticky to
// the *end* of the previous element. So all of the
// nonsense we have worked on is to keep things
// together.
if (!empty($node->is_whitespace)) {
$ws_accum[] = $node;
}
break; break;
} }
$collection = array();
$is_collecting = false;
$tag_index = 0;
} else {
// add the node to the collection
$collection[] = $token;
}
} }
// terminate if (empty($content)) {
if ($token === false) break; return false;
if ($is_child) {
// determine what we're dealing with
if ($token->name == 'col') {
// the only empty tag in the possie, we can handle it
// immediately
$cols[] = array_merge($collection, array($token));
$collection = array();
$tag_index = 0;
continue;
} }
switch($token->name) {
case 'caption': $ret = $initial_ws;
case 'colgroup': if ($caption !== false) {
case 'thead': $ret[] = $caption;
case 'tfoot': $ret = array_merge($ret, $after_caption_ws);
}
if ($cols !== false) {
$ret = array_merge($ret, $cols);
}
if ($thead !== false) {
$ret[] = $thead;
$ret = array_merge($ret, $after_thead_ws);
}
if ($tfoot !== false) {
$ret[] = $tfoot;
$ret = array_merge($ret, $after_tfoot_ws);
}
if ($tbody_mode) {
// we have to shuffle tr into tbody
$current_tr_tbody = null;
foreach($content as $node) {
switch ($node->name) {
case 'tbody': case 'tbody':
$current_tr_tbody = null;
$ret[] = $node;
break;
case 'tr': case 'tr':
$is_collecting = true; if ($current_tr_tbody === null) {
$collection[] = $token; $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
continue; $ret[] = $current_tr_tbody;
default:
if (!empty($token->is_whitespace)) {
$collection[] = $token;
$tag_index++;
} }
continue; $current_tr_tbody->children[] = $node;
break;
case '#PCDATA':
assert($node->is_whitespace);
if ($current_tr_tbody === null) {
$ret[] = $node;
} else {
$current_tr_tbody->children[] = $node;
}
break;
} }
} }
} else {
$ret = array_merge($ret, $content);
} }
if (empty($content)) return false; return $ret;
$ret = array();
if ($caption !== false) $ret = array_merge($ret, $caption);
if ($cols !== false) foreach ($cols as $token_array) $ret = array_merge($ret, $token_array);
if ($thead !== false) $ret = array_merge($ret, $thead);
if ($tfoot !== false) $ret = array_merge($ret, $tfoot);
foreach ($content as $token_array) $ret = array_merge($ret, $token_array);
if (!empty($collection) && $is_collecting == false){
// grab the trailing space
$ret = array_merge($ret, $collection);
}
array_pop($tokens_of_children); // remove phantom token
return ($ret === $tokens_of_children) ? true : $ret;
} }
} }

View File

@ -19,77 +19,92 @@ class HTMLPurifier_Config
/** /**
* HTML Purifier's version * HTML Purifier's version
* @type string
*/ */
public $version = '4.1.1'; public $version = '4.6.0';
/** /**
* Bool indicator whether or not to automatically finalize * Whether or not to automatically finalize
* the object if a read operation is done * the object if a read operation is done.
* @type bool
*/ */
public $autoFinalize = true; public $autoFinalize = true;
// protected member variables // protected member variables
/** /**
* Namespace indexed array of serials for specific namespaces (see * Namespace indexed array of serials for specific namespaces.
* getSerial() for more info). * @see getSerial() for more info.
* @type string[]
*/ */
protected $serials = array(); protected $serials = array();
/** /**
* Serial for entire configuration object * Serial for entire configuration object.
* @type string
*/ */
protected $serial; protected $serial;
/** /**
* Parser for variables * Parser for variables.
* @type HTMLPurifier_VarParser_Flexible
*/ */
protected $parser; protected $parser = null;
/** /**
* Reference HTMLPurifier_ConfigSchema for value checking * Reference HTMLPurifier_ConfigSchema for value checking.
* @type HTMLPurifier_ConfigSchema
* @note This is public for introspective purposes. Please don't * @note This is public for introspective purposes. Please don't
* abuse! * abuse!
*/ */
public $def; public $def;
/** /**
* Indexed array of definitions * Indexed array of definitions.
* @type HTMLPurifier_Definition[]
*/ */
protected $definitions; protected $definitions;
/** /**
* Bool indicator whether or not config is finalized * Whether or not config is finalized.
* @type bool
*/ */
protected $finalized = false; protected $finalized = false;
/** /**
* Property list containing configuration directives. * Property list containing configuration directives.
* @type array
*/ */
protected $plist; protected $plist;
/** /**
* Whether or not a set is taking place due to an * Whether or not a set is taking place due to an alias lookup.
* alias lookup. * @type bool
*/ */
private $aliasMode; private $aliasMode;
/** /**
* Set to false if you do not want line and file numbers in errors * Set to false if you do not want line and file numbers in errors.
* (useful when unit testing) * (useful when unit testing). This will also compress some errors
* and exceptions.
* @type bool
*/ */
public $chatty = true; public $chatty = true;
/** /**
* Current lock; only gets to this namespace are allowed. * Current lock; only gets to this namespace are allowed.
* @type string
*/ */
private $lock; private $lock;
/** /**
* @param $definition HTMLPurifier_ConfigSchema that defines what directives * Constructor
* are allowed. * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
* what directives are allowed.
* @param HTMLPurifier_PropertyList $parent
*/ */
public function __construct($definition, $parent = null) { public function __construct($definition, $parent = null)
{
$parent = $parent ? $parent : $definition->defaultPlist; $parent = $parent ? $parent : $definition->defaultPlist;
$this->plist = new HTMLPurifier_PropertyList($parent); $this->plist = new HTMLPurifier_PropertyList($parent);
$this->def = $definition; // keep a copy around for checking $this->def = $definition; // keep a copy around for checking
@ -102,10 +117,11 @@ class HTMLPurifier_Config
* object. Can be: a HTMLPurifier_Config() object, * object. Can be: a HTMLPurifier_Config() object,
* an array of directives based on loadArray(), * an array of directives based on loadArray(),
* or a string filename of an ini file. * or a string filename of an ini file.
* @param HTMLPurifier_ConfigSchema Schema object * @param HTMLPurifier_ConfigSchema $schema Schema object
* @return Configured HTMLPurifier_Config object * @return HTMLPurifier_Config Configured object
*/ */
public static function create($config, $schema = null) { public static function create($config, $schema = null)
{
if ($config instanceof HTMLPurifier_Config) { if ($config instanceof HTMLPurifier_Config) {
// pass-through // pass-through
return $config; return $config;
@ -115,57 +131,79 @@ class HTMLPurifier_Config
} else { } else {
$ret = new HTMLPurifier_Config($schema); $ret = new HTMLPurifier_Config($schema);
} }
if (is_string($config)) $ret->loadIni($config); if (is_string($config)) {
elseif (is_array($config)) $ret->loadArray($config); $ret->loadIni($config);
} elseif (is_array($config)) $ret->loadArray($config);
return $ret; return $ret;
} }
/** /**
* Creates a new config object that inherits from a previous one. * Creates a new config object that inherits from a previous one.
* @param HTMLPurifier_Config $config Configuration object to inherit * @param HTMLPurifier_Config $config Configuration object to inherit from.
* from.
* @return HTMLPurifier_Config object with $config as its parent. * @return HTMLPurifier_Config object with $config as its parent.
*/ */
public static function inherit(HTMLPurifier_Config $config) { public static function inherit(HTMLPurifier_Config $config)
{
return new HTMLPurifier_Config($config->def, $config->plist); return new HTMLPurifier_Config($config->def, $config->plist);
} }
/** /**
* Convenience constructor that creates a default configuration object. * Convenience constructor that creates a default configuration object.
* @return Default HTMLPurifier_Config object. * @return HTMLPurifier_Config default object.
*/ */
public static function createDefault() { public static function createDefault()
{
$definition = HTMLPurifier_ConfigSchema::instance(); $definition = HTMLPurifier_ConfigSchema::instance();
$config = new HTMLPurifier_Config($definition); $config = new HTMLPurifier_Config($definition);
return $config; return $config;
} }
/** /**
* Retreives a value from the configuration. * Retrieves a value from the configuration.
* @param $key String key *
* @param string $key String key
* @param mixed $a
*
* @return mixed
*/ */
public function get($key, $a = null) { public function get($key, $a = null)
{
if ($a !== null) { if ($a !== null) {
$this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING); $this->triggerError(
"Using deprecated API: use \$config->get('$key.$a') instead",
E_USER_WARNING
);
$key = "$key.$a"; $key = "$key.$a";
} }
if (!$this->finalized) $this->autoFinalize(); if (!$this->finalized) {
$this->autoFinalize();
}
if (!isset($this->def->info[$key])) { if (!isset($this->def->info[$key])) {
// can't add % due to SimpleTest bug // can't add % due to SimpleTest bug
$this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key), $this->triggerError(
E_USER_WARNING); 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
E_USER_WARNING
);
return; return;
} }
if (isset($this->def->info[$key]->isAlias)) { if (isset($this->def->info[$key]->isAlias)) {
$d = $this->def->info[$key]; $d = $this->def->info[$key];
$this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key, $this->triggerError(
E_USER_ERROR); 'Cannot get value from aliased directive, use real name ' . $d->key,
E_USER_ERROR
);
return; return;
} }
if ($this->lock) { if ($this->lock) {
list($ns) = explode('.', $key); list($ns) = explode('.', $key);
if ($ns !== $this->lock) { if ($ns !== $this->lock) {
$this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR); $this->triggerError(
'Cannot get value of namespace ' . $ns . ' when lock for ' .
$this->lock .
' is active, this probably indicates a Definition setup method ' .
'is accessing directives that are not within its namespace',
E_USER_ERROR
);
return; return;
} }
} }
@ -173,53 +211,73 @@ class HTMLPurifier_Config
} }
/** /**
* Retreives an array of directives to values from a given namespace * Retrieves an array of directives to values from a given namespace
* @param $namespace String namespace *
* @param string $namespace String namespace
*
* @return array
*/ */
public function getBatch($namespace) { public function getBatch($namespace)
if (!$this->finalized) $this->autoFinalize(); {
if (!$this->finalized) {
$this->autoFinalize();
}
$full = $this->getAll(); $full = $this->getAll();
if (!isset($full[$namespace])) { if (!isset($full[$namespace])) {
$this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace), $this->triggerError(
E_USER_WARNING); 'Cannot retrieve undefined namespace ' .
htmlspecialchars($namespace),
E_USER_WARNING
);
return; return;
} }
return $full[$namespace]; return $full[$namespace];
} }
/** /**
* Returns a md5 signature of a segment of the configuration object * Returns a SHA-1 signature of a segment of the configuration object
* that uniquely identifies that particular configuration * that uniquely identifies that particular configuration
*
* @param string $namespace Namespace to get serial for
*
* @return string
* @note Revision is handled specially and is removed from the batch * @note Revision is handled specially and is removed from the batch
* before processing! * before processing!
* @param $namespace Namespace to get serial for
*/ */
public function getBatchSerial($namespace) { public function getBatchSerial($namespace)
{
if (empty($this->serials[$namespace])) { if (empty($this->serials[$namespace])) {
$batch = $this->getBatch($namespace); $batch = $this->getBatch($namespace);
unset($batch['DefinitionRev']); unset($batch['DefinitionRev']);
$this->serials[$namespace] = md5(serialize($batch)); $this->serials[$namespace] = sha1(serialize($batch));
} }
return $this->serials[$namespace]; return $this->serials[$namespace];
} }
/** /**
* Returns a md5 signature for the entire configuration object * Returns a SHA-1 signature for the entire configuration object
* that uniquely identifies that particular configuration * that uniquely identifies that particular configuration
*
* @return string
*/ */
public function getSerial() { public function getSerial()
{
if (empty($this->serial)) { if (empty($this->serial)) {
$this->serial = md5(serialize($this->getAll())); $this->serial = sha1(serialize($this->getAll()));
} }
return $this->serial; return $this->serial;
} }
/** /**
* Retrieves all directives, organized by namespace * Retrieves all directives, organized by namespace
*
* @warning This is a pretty inefficient function, avoid if you can * @warning This is a pretty inefficient function, avoid if you can
*/ */
public function getAll() { public function getAll()
if (!$this->finalized) $this->autoFinalize(); {
if (!$this->finalized) {
$this->autoFinalize();
}
$ret = array(); $ret = array();
foreach ($this->plist->squash() as $name => $value) { foreach ($this->plist->squash() as $name => $value) {
list($ns, $key) = explode('.', $name, 2); list($ns, $key) = explode('.', $name, 2);
@ -230,10 +288,13 @@ class HTMLPurifier_Config
/** /**
* Sets a value to configuration. * Sets a value to configuration.
* @param $key String key *
* @param $value Mixed value * @param string $key key
* @param mixed $value value
* @param mixed $a
*/ */
public function set($key, $value, $a = null) { public function set($key, $value, $a = null)
{
if (strpos($key, '.') === false) { if (strpos($key, '.') === false) {
$namespace = $key; $namespace = $key;
$directive = $value; $directive = $value;
@ -243,18 +304,25 @@ class HTMLPurifier_Config
} else { } else {
list($namespace) = explode('.', $key); list($namespace) = explode('.', $key);
} }
if ($this->isFinalized('Cannot set directive after finalization')) return; if ($this->isFinalized('Cannot set directive after finalization')) {
return;
}
if (!isset($this->def->info[$key])) { if (!isset($this->def->info[$key])) {
$this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', $this->triggerError(
E_USER_WARNING); 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
E_USER_WARNING
);
return; return;
} }
$def = $this->def->info[$key]; $def = $this->def->info[$key];
if (isset($def->isAlias)) { if (isset($def->isAlias)) {
if ($this->aliasMode) { if ($this->aliasMode) {
$this->triggerError('Double-aliases not allowed, please fix '. $this->triggerError(
'ConfigSchema bug with' . $key, E_USER_ERROR); 'Double-aliases not allowed, please fix '.
'ConfigSchema bug with' . $key,
E_USER_ERROR
);
return; return;
} }
$this->aliasMode = true; $this->aliasMode = true;
@ -278,7 +346,11 @@ class HTMLPurifier_Config
try { try {
$value = $this->parser->parse($value, $type, $allow_null); $value = $this->parser->parse($value, $type, $allow_null);
} catch (HTMLPurifier_VarParserException $e) { } catch (HTMLPurifier_VarParserException $e) {
$this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING); $this->triggerError(
'Value for ' . $key . ' is of invalid type, should be ' .
HTMLPurifier_VarParser::getTypeName($type),
E_USER_WARNING
);
return; return;
} }
if (is_string($value) && is_object($def)) { if (is_string($value) && is_object($def)) {
@ -288,8 +360,11 @@ class HTMLPurifier_Config
} }
// check to see if the value is allowed // check to see if the value is allowed
if (isset($def->allowed) && !isset($def->allowed[$value])) { if (isset($def->allowed) && !isset($def->allowed[$value])) {
$this->triggerError('Value not supported, valid values are: ' . $this->triggerError(
$this->_listify($def->allowed), E_USER_WARNING); 'Value not supported, valid values are: ' .
$this->_listify($def->allowed),
E_USER_WARNING
);
return; return;
} }
} }
@ -307,38 +382,102 @@ class HTMLPurifier_Config
/** /**
* Convenience function for error reporting * Convenience function for error reporting
*
* @param array $lookup
*
* @return string
*/ */
private function _listify($lookup) { private function _listify($lookup)
{
$list = array(); $list = array();
foreach ($lookup as $name => $b) $list[] = $name; foreach ($lookup as $name => $b) {
$list[] = $name;
}
return implode(', ', $list); return implode(', ', $list);
} }
/** /**
* Retrieves object reference to the HTML definition. * Retrieves object reference to the HTML definition.
* @param $raw Return a copy that has not been setup yet. Must be *
* @param bool $raw Return a copy that has not been setup yet. Must be
* called before it's been setup, otherwise won't work. * called before it's been setup, otherwise won't work.
* @param bool $optimized If true, this method may return null, to
* indicate that a cached version of the modified
* definition object is available and no further edits
* are necessary. Consider using
* maybeGetRawHTMLDefinition, which is more explicitly
* named, instead.
*
* @return HTMLPurifier_HTMLDefinition
*/ */
public function getHTMLDefinition($raw = false) { public function getHTMLDefinition($raw = false, $optimized = false)
return $this->getDefinition('HTML', $raw); {
return $this->getDefinition('HTML', $raw, $optimized);
} }
/** /**
* Retrieves object reference to the CSS definition * Retrieves object reference to the CSS definition
* @param $raw Return a copy that has not been setup yet. Must be *
* @param bool $raw Return a copy that has not been setup yet. Must be
* called before it's been setup, otherwise won't work. * called before it's been setup, otherwise won't work.
* @param bool $optimized If true, this method may return null, to
* indicate that a cached version of the modified
* definition object is available and no further edits
* are necessary. Consider using
* maybeGetRawCSSDefinition, which is more explicitly
* named, instead.
*
* @return HTMLPurifier_CSSDefinition
*/ */
public function getCSSDefinition($raw = false) { public function getCSSDefinition($raw = false, $optimized = false)
return $this->getDefinition('CSS', $raw); {
return $this->getDefinition('CSS', $raw, $optimized);
}
/**
* Retrieves object reference to the URI definition
*
* @param bool $raw Return a copy that has not been setup yet. Must be
* called before it's been setup, otherwise won't work.
* @param bool $optimized If true, this method may return null, to
* indicate that a cached version of the modified
* definition object is available and no further edits
* are necessary. Consider using
* maybeGetRawURIDefinition, which is more explicitly
* named, instead.
*
* @return HTMLPurifier_URIDefinition
*/
public function getURIDefinition($raw = false, $optimized = false)
{
return $this->getDefinition('URI', $raw, $optimized);
} }
/** /**
* Retrieves a definition * Retrieves a definition
* @param $type Type of definition: HTML, CSS, etc *
* @param $raw Whether or not definition should be returned raw * @param string $type Type of definition: HTML, CSS, etc
* @param bool $raw Whether or not definition should be returned raw
* @param bool $optimized Only has an effect when $raw is true. Whether
* or not to return null if the result is already present in
* the cache. This is off by default for backwards
* compatibility reasons, but you need to do things this
* way in order to ensure that caching is done properly.
* Check out enduser-customize.html for more details.
* We probably won't ever change this default, as much as the
* maybe semantics is the "right thing to do."
*
* @throws HTMLPurifier_Exception
* @return HTMLPurifier_Definition
*/ */
public function getDefinition($type, $raw = false) { public function getDefinition($type, $raw = false, $optimized = false)
if (!$this->finalized) $this->autoFinalize(); {
if ($optimized && !$raw) {
throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
}
if (!$this->finalized) {
$this->autoFinalize();
}
// temporarily suspend locks, so we can handle recursive definition calls // temporarily suspend locks, so we can handle recursive definition calls
$lock = $this->lock; $lock = $this->lock;
$this->lock = null; $this->lock = null;
@ -346,61 +485,193 @@ class HTMLPurifier_Config
$cache = $factory->create($type, $this); $cache = $factory->create($type, $this);
$this->lock = $lock; $this->lock = $lock;
if (!$raw) { if (!$raw) {
// see if we can quickly supply a definition // full definition
// ---------------
// check if definition is in memory
if (!empty($this->definitions[$type])) { if (!empty($this->definitions[$type])) {
if (!$this->definitions[$type]->setup) { $def = $this->definitions[$type];
$this->definitions[$type]->setup($this); // check if the definition is setup
$cache->set($this->definitions[$type], $this); if ($def->setup) {
} return $def;
return $this->definitions[$type];
}
// memory check missed, try cache
$this->definitions[$type] = $cache->get($this);
if ($this->definitions[$type]) {
// definition in cache, return it
return $this->definitions[$type];
}
} elseif (
!empty($this->definitions[$type]) &&
!$this->definitions[$type]->setup
) {
// raw requested, raw in memory, quick return
return $this->definitions[$type];
}
// quick checks failed, let's create the object
if ($type == 'HTML') {
$this->definitions[$type] = new HTMLPurifier_HTMLDefinition();
} elseif ($type == 'CSS') {
$this->definitions[$type] = new HTMLPurifier_CSSDefinition();
} elseif ($type == 'URI') {
$this->definitions[$type] = new HTMLPurifier_URIDefinition();
} else { } else {
throw new HTMLPurifier_Exception("Definition of $type type not supported"); $def->setup($this);
if ($def->optimized) {
$cache->add($def, $this);
} }
// quick abort if raw return $def;
if ($raw) {
if (is_null($this->get($type . '.DefinitionID'))) {
// fatally error out if definition ID not set
throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID");
} }
return $this->definitions[$type];
} }
// check if definition is in cache
$def = $cache->get($this);
if ($def) {
// definition in cache, save to memory and return it
$this->definitions[$type] = $def;
return $def;
}
// initialize it
$def = $this->initDefinition($type);
// set it up // set it up
$this->lock = $type; $this->lock = $type;
$this->definitions[$type]->setup($this); $def->setup($this);
$this->lock = null; $this->lock = null;
// save in cache // save in cache
$cache->set($this->definitions[$type], $this); $cache->add($def, $this);
return $this->definitions[$type]; // return it
return $def;
} else {
// raw definition
// --------------
// check preconditions
$def = null;
if ($optimized) {
if (is_null($this->get($type . '.DefinitionID'))) {
// fatally error out if definition ID not set
throw new HTMLPurifier_Exception(
"Cannot retrieve raw version without specifying %$type.DefinitionID"
);
}
}
if (!empty($this->definitions[$type])) {
$def = $this->definitions[$type];
if ($def->setup && !$optimized) {
$extra = $this->chatty ?
" (try moving this code block earlier in your initialization)" :
"";
throw new HTMLPurifier_Exception(
"Cannot retrieve raw definition after it has already been setup" .
$extra
);
}
if ($def->optimized === null) {
$extra = $this->chatty ? " (try flushing your cache)" : "";
throw new HTMLPurifier_Exception(
"Optimization status of definition is unknown" . $extra
);
}
if ($def->optimized !== $optimized) {
$msg = $optimized ? "optimized" : "unoptimized";
$extra = $this->chatty ?
" (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
: "";
throw new HTMLPurifier_Exception(
"Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
);
}
}
// check if definition was in memory
if ($def) {
if ($def->setup) {
// invariant: $optimized === true (checked above)
return null;
} else {
return $def;
}
}
// if optimized, check if definition was in cache
// (because we do the memory check first, this formulation
// is prone to cache slamming, but I think
// guaranteeing that either /all/ of the raw
// setup code or /none/ of it is run is more important.)
if ($optimized) {
// This code path only gets run once; once we put
// something in $definitions (which is guaranteed by the
// trailing code), we always short-circuit above.
$def = $cache->get($this);
if ($def) {
// save the full definition for later, but don't
// return it yet
$this->definitions[$type] = $def;
return null;
}
}
// check invariants for creation
if (!$optimized) {
if (!is_null($this->get($type . '.DefinitionID'))) {
if ($this->chatty) {
$this->triggerError(
'Due to a documentation error in previous version of HTML Purifier, your ' .
'definitions are not being cached. If this is OK, you can remove the ' .
'%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
'modify your code to use maybeGetRawDefinition, and test if the returned ' .
'value is null before making any edits (if it is null, that means that a ' .
'cached version is available, and no raw operations are necessary). See ' .
'<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
'Customize</a> for more details',
E_USER_WARNING
);
} else {
$this->triggerError(
"Useless DefinitionID declaration",
E_USER_WARNING
);
}
}
}
// initialize it
$def = $this->initDefinition($type);
$def->optimized = $optimized;
return $def;
}
throw new HTMLPurifier_Exception("The impossible happened!");
}
/**
* Initialise definition
*
* @param string $type What type of definition to create
*
* @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
* @throws HTMLPurifier_Exception
*/
private function initDefinition($type)
{
// quick checks failed, let's create the object
if ($type == 'HTML') {
$def = new HTMLPurifier_HTMLDefinition();
} elseif ($type == 'CSS') {
$def = new HTMLPurifier_CSSDefinition();
} elseif ($type == 'URI') {
$def = new HTMLPurifier_URIDefinition();
} else {
throw new HTMLPurifier_Exception(
"Definition of $type type not supported"
);
}
$this->definitions[$type] = $def;
return $def;
}
public function maybeGetRawDefinition($name)
{
return $this->getDefinition($name, true, true);
}
public function maybeGetRawHTMLDefinition()
{
return $this->getDefinition('HTML', true, true);
}
public function maybeGetRawCSSDefinition()
{
return $this->getDefinition('CSS', true, true);
}
public function maybeGetRawURIDefinition()
{
return $this->getDefinition('URI', true, true);
} }
/** /**
* Loads configuration values from an array with the following structure: * Loads configuration values from an array with the following structure:
* Namespace.Directive => Value * Namespace.Directive => Value
* @param $config_array Configuration associative array *
* @param array $config_array Configuration associative array
*/ */
public function loadArray($config_array) { public function loadArray($config_array)
if ($this->isFinalized('Cannot load directives after finalization')) return; {
if ($this->isFinalized('Cannot load directives after finalization')) {
return;
}
foreach ($config_array as $key => $value) { foreach ($config_array as $key => $value) {
$key = str_replace('_', '.', $key); $key = str_replace('_', '.', $key);
if (strpos($key, '.') !== false) { if (strpos($key, '.') !== false) {
@ -408,8 +679,8 @@ class HTMLPurifier_Config
} else { } else {
$namespace = $key; $namespace = $key;
$namespace_values = $value; $namespace_values = $value;
foreach ($namespace_values as $directive => $value) { foreach ($namespace_values as $directive => $value2) {
$this->set($namespace .'.'. $directive, $value); $this->set($namespace .'.'. $directive, $value2);
} }
} }
} }
@ -419,14 +690,21 @@ class HTMLPurifier_Config
* Returns a list of array(namespace, directive) for all directives * Returns a list of array(namespace, directive) for all directives
* that are allowed in a web-form context as per an allowed * that are allowed in a web-form context as per an allowed
* namespaces/directives list. * namespaces/directives list.
* @param $allowed List of allowed namespaces/directives *
* @param array $allowed List of allowed namespaces/directives
* @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
*
* @return array
*/ */
public static function getAllowedDirectivesForForm($allowed, $schema = null) { public static function getAllowedDirectivesForForm($allowed, $schema = null)
{
if (!$schema) { if (!$schema) {
$schema = HTMLPurifier_ConfigSchema::instance(); $schema = HTMLPurifier_ConfigSchema::instance();
} }
if ($allowed !== true) { if ($allowed !== true) {
if (is_string($allowed)) $allowed = array($allowed); if (is_string($allowed)) {
$allowed = array($allowed);
}
$allowed_ns = array(); $allowed_ns = array();
$allowed_directives = array(); $allowed_directives = array();
$blacklisted_directives = array(); $blacklisted_directives = array();
@ -448,11 +726,19 @@ class HTMLPurifier_Config
foreach ($schema->info as $key => $def) { foreach ($schema->info as $key => $def) {
list($ns, $directive) = explode('.', $key, 2); list($ns, $directive) = explode('.', $key, 2);
if ($allowed !== true) { if ($allowed !== true) {
if (isset($blacklisted_directives["$ns.$directive"])) continue; if (isset($blacklisted_directives["$ns.$directive"])) {
if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue; continue;
}
if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
continue;
}
}
if (isset($def->isAlias)) {
continue;
}
if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
continue;
} }
if (isset($def->isAlias)) continue;
if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
$ret[] = array($ns, $directive); $ret[] = array($ns, $directive);
} }
return $ret; return $ret;
@ -461,13 +747,17 @@ class HTMLPurifier_Config
/** /**
* Loads configuration values from $_GET/$_POST that were posted * Loads configuration values from $_GET/$_POST that were posted
* via ConfigForm * via ConfigForm
* @param $array $_GET or $_POST array to import *
* @param $index Index/name that the config variables are in * @param array $array $_GET or $_POST array to import
* @param $allowed List of allowed namespaces/directives * @param string|bool $index Index/name that the config variables are in
* @param $mq_fix Boolean whether or not to enable magic quotes fix * @param array|bool $allowed List of allowed namespaces/directives
* @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
* @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
*
* @return mixed
*/ */
public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) { public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
{
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
$config = HTMLPurifier_Config::create($ret, $schema); $config = HTMLPurifier_Config::create($ret, $schema);
return $config; return $config;
@ -475,9 +765,14 @@ class HTMLPurifier_Config
/** /**
* Merges in configuration values from $_GET/$_POST to object. NOT STATIC. * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
* @note Same parameters as loadArrayFromForm *
* @param array $array $_GET or $_POST array to import
* @param string|bool $index Index/name that the config variables are in
* @param array|bool $allowed List of allowed namespaces/directives
* @param bool $mq_fix Boolean whether or not to enable magic quotes fix
*/ */
public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) { public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
{
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
$this->loadArray($ret); $this->loadArray($ret);
} }
@ -485,9 +780,20 @@ class HTMLPurifier_Config
/** /**
* Prepares an array from a form into something usable for the more * Prepares an array from a form into something usable for the more
* strict parts of HTMLPurifier_Config * strict parts of HTMLPurifier_Config
*
* @param array $array $_GET or $_POST array to import
* @param string|bool $index Index/name that the config variables are in
* @param array|bool $allowed List of allowed namespaces/directives
* @param bool $mq_fix Boolean whether or not to enable magic quotes fix
* @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
*
* @return array
*/ */
public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) { public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); {
if ($index !== false) {
$array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
}
$mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
@ -499,7 +805,9 @@ class HTMLPurifier_Config
$ret[$ns][$directive] = null; $ret[$ns][$directive] = null;
continue; continue;
} }
if (!isset($array[$skey])) continue; if (!isset($array[$skey])) {
continue;
}
$value = $mq ? stripslashes($array[$skey]) : $array[$skey]; $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
$ret[$ns][$directive] = $value; $ret[$ns][$directive] = $value;
} }
@ -508,19 +816,27 @@ class HTMLPurifier_Config
/** /**
* Loads configuration values from an ini file * Loads configuration values from an ini file
* @param $filename Name of ini file *
* @param string $filename Name of ini file
*/ */
public function loadIni($filename) { public function loadIni($filename)
if ($this->isFinalized('Cannot load directives after finalization')) return; {
if ($this->isFinalized('Cannot load directives after finalization')) {
return;
}
$array = parse_ini_file($filename, true); $array = parse_ini_file($filename, true);
$this->loadArray($array); $this->loadArray($array);
} }
/** /**
* Checks whether or not the configuration object is finalized. * Checks whether or not the configuration object is finalized.
* @param $error String error message, or false for no error *
* @param string|bool $error String error message, or false for no error
*
* @return bool
*/ */
public function isFinalized($error = false) { public function isFinalized($error = false)
{
if ($this->finalized && $error) { if ($this->finalized && $error) {
$this->triggerError($error, E_USER_ERROR); $this->triggerError($error, E_USER_ERROR);
} }
@ -531,7 +847,8 @@ class HTMLPurifier_Config
* Finalizes configuration only if auto finalize is on and not * Finalizes configuration only if auto finalize is on and not
* already finalized * already finalized
*/ */
public function autoFinalize() { public function autoFinalize()
{
if ($this->autoFinalize) { if ($this->autoFinalize) {
$this->finalize(); $this->finalize();
} else { } else {
@ -542,24 +859,35 @@ class HTMLPurifier_Config
/** /**
* Finalizes a configuration object, prohibiting further change * Finalizes a configuration object, prohibiting further change
*/ */
public function finalize() { public function finalize()
{
$this->finalized = true; $this->finalized = true;
unset($this->parser); $this->parser = null;
} }
/** /**
* Produces a nicely formatted error message by supplying the * Produces a nicely formatted error message by supplying the
* stack frame information from two levels up and OUTSIDE of * stack frame information OUTSIDE of HTMLPurifier_Config.
* HTMLPurifier_Config. *
* @param string $msg An error message
* @param int $no An error number
*/ */
protected function triggerError($msg, $no) { protected function triggerError($msg, $no)
{
// determine previous stack frame // determine previous stack frame
$backtrace = debug_backtrace();
if ($this->chatty && isset($backtrace[1])) {
$frame = $backtrace[1];
$extra = " on line {$frame['line']} in file {$frame['file']}";
} else {
$extra = ''; $extra = '';
if ($this->chatty) {
$trace = debug_backtrace();
// zip(tail(trace), trace) -- but PHP is not Haskell har har
for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
// XXX this is not correct on some versions of HTML Purifier
if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
continue;
}
$frame = $trace[$i];
$extra = " invoked on line {$frame['line']} in file {$frame['file']}";
break;
}
} }
trigger_error($msg . $extra, $no); trigger_error($msg . $extra, $no);
} }
@ -567,8 +895,11 @@ class HTMLPurifier_Config
/** /**
* Returns a serialized form of the configuration object that can * Returns a serialized form of the configuration object that can
* be reconstituted. * be reconstituted.
*
* @return string
*/ */
public function serialize() { public function serialize()
{
$this->getDefinition('HTML'); $this->getDefinition('HTML');
$this->getDefinition('CSS'); $this->getDefinition('CSS');
$this->getDefinition('URI'); $this->getDefinition('URI');

View File

@ -3,21 +3,24 @@
/** /**
* Configuration definition, defines directives and their defaults. * Configuration definition, defines directives and their defaults.
*/ */
class HTMLPurifier_ConfigSchema { class HTMLPurifier_ConfigSchema
{
/** /**
* Defaults of the directives and namespaces. * Defaults of the directives and namespaces.
* @type array
* @note This shares the exact same structure as HTMLPurifier_Config::$conf * @note This shares the exact same structure as HTMLPurifier_Config::$conf
*/ */
public $defaults = array(); public $defaults = array();
/** /**
* The default property list. Do not edit this property list. * The default property list. Do not edit this property list.
* @type array
*/ */
public $defaultPlist; public $defaultPlist;
/** /**
* Definition of the directives. The structure of this is: * Definition of the directives.
* The structure of this is:
* *
* array( * array(
* 'Namespace' => array( * 'Namespace' => array(
@ -44,29 +47,43 @@ class HTMLPurifier_ConfigSchema {
* This class is friendly with HTMLPurifier_Config. If you need introspection * This class is friendly with HTMLPurifier_Config. If you need introspection
* about the schema, you're better of using the ConfigSchema_Interchange, * about the schema, you're better of using the ConfigSchema_Interchange,
* which uses more memory but has much richer information. * which uses more memory but has much richer information.
* @type array
*/ */
public $info = array(); public $info = array();
/** /**
* Application-wide singleton * Application-wide singleton
* @type HTMLPurifier_ConfigSchema
*/ */
static protected $singleton; protected static $singleton;
public function __construct() { public function __construct()
{
$this->defaultPlist = new HTMLPurifier_PropertyList(); $this->defaultPlist = new HTMLPurifier_PropertyList();
} }
/** /**
* Unserializes the default ConfigSchema. * Unserializes the default ConfigSchema.
* @return HTMLPurifier_ConfigSchema
*/ */
public static function makeFromSerial() { public static function makeFromSerial()
return unserialize(file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser')); {
$contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
$r = unserialize($contents);
if (!$r) {
$hash = sha1($contents);
trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
}
return $r;
} }
/** /**
* Retrieves an instance of the application-wide configuration definition. * Retrieves an instance of the application-wide configuration definition.
* @param HTMLPurifier_ConfigSchema $prototype
* @return HTMLPurifier_ConfigSchema
*/ */
public static function instance($prototype = null) { public static function instance($prototype = null)
{
if ($prototype !== null) { if ($prototype !== null) {
HTMLPurifier_ConfigSchema::$singleton = $prototype; HTMLPurifier_ConfigSchema::$singleton = $prototype;
} elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
@ -80,17 +97,19 @@ class HTMLPurifier_ConfigSchema {
* @warning Will fail of directive's namespace is defined. * @warning Will fail of directive's namespace is defined.
* @warning This method's signature is slightly different from the legacy * @warning This method's signature is slightly different from the legacy
* define() static method! Beware! * define() static method! Beware!
* @param $namespace Namespace the directive is in * @param string $key Name of directive
* @param $name Key of directive * @param mixed $default Default value of directive
* @param $default Default value of directive * @param string $type Allowed type of the directive. See
* @param $type Allowed type of the directive. See
* HTMLPurifier_DirectiveDef::$type for allowed values * HTMLPurifier_DirectiveDef::$type for allowed values
* @param $allow_null Whether or not to allow null values * @param bool $allow_null Whether or not to allow null values
*/ */
public function add($key, $default, $type, $allow_null) { public function add($key, $default, $type, $allow_null)
{
$obj = new stdclass(); $obj = new stdclass();
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type]; $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
if ($allow_null) $obj->allow_null = true; if ($allow_null) {
$obj->allow_null = true;
}
$this->info[$key] = $obj; $this->info[$key] = $obj;
$this->defaults[$key] = $default; $this->defaults[$key] = $default;
$this->defaultPlist->set($key, $default); $this->defaultPlist->set($key, $default);
@ -101,11 +120,11 @@ class HTMLPurifier_ConfigSchema {
* *
* Directive value aliases are convenient for developers because it lets * Directive value aliases are convenient for developers because it lets
* them set a directive to several values and get the same result. * them set a directive to several values and get the same result.
* @param $namespace Directive's namespace * @param string $key Name of Directive
* @param $name Name of Directive * @param array $aliases Hash of aliased values to the real alias
* @param $aliases Hash of aliased values to the real alias
*/ */
public function addValueAliases($key, $aliases) { public function addValueAliases($key, $aliases)
{
if (!isset($this->info[$key]->aliases)) { if (!isset($this->info[$key]->aliases)) {
$this->info[$key]->aliases = array(); $this->info[$key]->aliases = array();
} }
@ -118,22 +137,21 @@ class HTMLPurifier_ConfigSchema {
* Defines a set of allowed values for a directive. * Defines a set of allowed values for a directive.
* @warning This is slightly different from the corresponding static * @warning This is slightly different from the corresponding static
* method definition. * method definition.
* @param $namespace Namespace of directive * @param string $key Name of directive
* @param $name Name of directive * @param array $allowed Lookup array of allowed values
* @param $allowed Lookup array of allowed values
*/ */
public function addAllowedValues($key, $allowed) { public function addAllowedValues($key, $allowed)
{
$this->info[$key]->allowed = $allowed; $this->info[$key]->allowed = $allowed;
} }
/** /**
* Defines a directive alias for backwards compatibility * Defines a directive alias for backwards compatibility
* @param $namespace * @param string $key Directive that will be aliased
* @param $name Directive that will be aliased * @param string $new_key Directive that the alias will be to
* @param $new_namespace
* @param $new_name Directive that the alias will be to
*/ */
public function addAlias($key, $new_key) { public function addAlias($key, $new_key)
{
$obj = new stdclass; $obj = new stdclass;
$obj->key = $new_key; $obj->key = $new_key;
$obj->isAlias = true; $obj->isAlias = true;
@ -143,7 +161,8 @@ class HTMLPurifier_ConfigSchema {
/** /**
* Replaces any stdclass that only has the type property with type integer. * Replaces any stdclass that only has the type property with type integer.
*/ */
public function postProcess() { public function postProcess()
{
foreach ($this->info as $key => $v) { foreach ($this->info as $key => $v) {
if (count((array) $v) == 1) { if (count((array) $v) == 1) {
$this->info[$key] = $v->type; $this->info[$key] = $v->type;
@ -152,7 +171,6 @@ class HTMLPurifier_ConfigSchema {
} }
} }
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,7 +7,12 @@
class HTMLPurifier_ConfigSchema_Builder_ConfigSchema class HTMLPurifier_ConfigSchema_Builder_ConfigSchema
{ {
public function build($interchange) { /**
* @param HTMLPurifier_ConfigSchema_Interchange $interchange
* @return HTMLPurifier_ConfigSchema
*/
public function build($interchange)
{
$schema = new HTMLPurifier_ConfigSchema(); $schema = new HTMLPurifier_ConfigSchema();
foreach ($interchange->directives as $d) { foreach ($interchange->directives as $d) {
$schema->add( $schema->add(
@ -38,7 +43,6 @@ class HTMLPurifier_ConfigSchema_Builder_ConfigSchema
$schema->postProcess(); $schema->postProcess();
return $schema; return $schema;
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,10 +7,21 @@
class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter
{ {
/**
* @type HTMLPurifier_ConfigSchema_Interchange
*/
protected $interchange; protected $interchange;
/**
* @type string
*/
private $namespace; private $namespace;
protected function writeHTMLDiv($html) { /**
* @param string $html
*/
protected function writeHTMLDiv($html)
{
$this->startElement('div'); $this->startElement('div');
$purifier = HTMLPurifier::getInstance(); $purifier = HTMLPurifier::getInstance();
@ -21,12 +32,23 @@ class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter
$this->endElement(); // div $this->endElement(); // div
} }
protected function export($var) { /**
if ($var === array()) return 'array()'; * @param mixed $var
* @return string
*/
protected function export($var)
{
if ($var === array()) {
return 'array()';
}
return var_export($var, true); return var_export($var, true);
} }
public function build($interchange) { /**
* @param HTMLPurifier_ConfigSchema_Interchange $interchange
*/
public function build($interchange)
{
// global access, only use as last resort // global access, only use as last resort
$this->interchange = $interchange; $this->interchange = $interchange;
@ -39,19 +61,26 @@ class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter
$this->buildDirective($directive); $this->buildDirective($directive);
} }
if ($this->namespace) $this->endElement(); // namespace if ($this->namespace) {
$this->endElement();
} // namespace
$this->endElement(); // configdoc $this->endElement(); // configdoc
$this->flush(); $this->flush();
} }
public function buildDirective($directive) { /**
* @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
*/
public function buildDirective($directive)
{
// Kludge, although I suppose having a notion of a "root namespace" // Kludge, although I suppose having a notion of a "root namespace"
// certainly makes things look nicer when documentation is built. // certainly makes things look nicer when documentation is built.
// Depends on things being sorted. // Depends on things being sorted.
if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) { if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) {
if ($this->namespace) $this->endElement(); // namespace if ($this->namespace) {
$this->endElement();
} // namespace
$this->namespace = $directive->id->getRootNamespace(); $this->namespace = $directive->id->getRootNamespace();
$this->startElement('namespace'); $this->startElement('namespace');
$this->writeAttribute('id', $this->namespace); $this->writeAttribute('id', $this->namespace);
@ -64,25 +93,35 @@ class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter
$this->writeElement('name', $directive->id->getDirective()); $this->writeElement('name', $directive->id->getDirective());
$this->startElement('aliases'); $this->startElement('aliases');
foreach ($directive->aliases as $alias) $this->writeElement('alias', $alias->toString()); foreach ($directive->aliases as $alias) {
$this->writeElement('alias', $alias->toString());
}
$this->endElement(); // aliases $this->endElement(); // aliases
$this->startElement('constraints'); $this->startElement('constraints');
if ($directive->version) $this->writeElement('version', $directive->version); if ($directive->version) {
$this->writeElement('version', $directive->version);
}
$this->startElement('type'); $this->startElement('type');
if ($directive->typeAllowsNull) $this->writeAttribute('allow-null', 'yes'); if ($directive->typeAllowsNull) {
$this->writeAttribute('allow-null', 'yes');
}
$this->text($directive->type); $this->text($directive->type);
$this->endElement(); // type $this->endElement(); // type
if ($directive->allowed) { if ($directive->allowed) {
$this->startElement('allowed'); $this->startElement('allowed');
foreach ($directive->allowed as $value => $x) $this->writeElement('value', $value); foreach ($directive->allowed as $value => $x) {
$this->writeElement('value', $value);
}
$this->endElement(); // allowed $this->endElement(); // allowed
} }
$this->writeElement('default', $this->export($directive->default)); $this->writeElement('default', $this->export($directive->default));
$this->writeAttribute('xml:space', 'preserve'); $this->writeAttribute('xml:space', 'preserve');
if ($directive->external) { if ($directive->external) {
$this->startElement('external'); $this->startElement('external');
foreach ($directive->external as $project) $this->writeElement('project', $project); foreach ($directive->external as $project) {
$this->writeElement('project', $project);
}
$this->endElement(); $this->endElement();
} }
$this->endElement(); // constraints $this->endElement(); // constraints
@ -100,7 +139,6 @@ class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter
$this->endElement(); // directive $this->endElement(); // directive
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -10,18 +10,23 @@ class HTMLPurifier_ConfigSchema_Interchange
/** /**
* Name of the application this schema is describing. * Name of the application this schema is describing.
* @type string
*/ */
public $name; public $name;
/** /**
* Array of Directive ID => array(directive info) * Array of Directive ID => array(directive info)
* @type HTMLPurifier_ConfigSchema_Interchange_Directive[]
*/ */
public $directives = array(); public $directives = array();
/** /**
* Adds a directive array to $directives * Adds a directive array to $directives
* @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
* @throws HTMLPurifier_ConfigSchema_Exception
*/ */
public function addDirective($directive) { public function addDirective($directive)
{
if (isset($this->directives[$i = $directive->id->toString()])) { if (isset($this->directives[$i = $directive->id->toString()])) {
throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'");
} }
@ -32,11 +37,11 @@ class HTMLPurifier_ConfigSchema_Interchange
* Convenience function to perform standard validation. Throws exception * Convenience function to perform standard validation. Throws exception
* on failed validation. * on failed validation.
*/ */
public function validate() { public function validate()
{
$validator = new HTMLPurifier_ConfigSchema_Validator(); $validator = new HTMLPurifier_ConfigSchema_Validator();
return $validator->validate($this); return $validator->validate($this);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -7,71 +7,83 @@ class HTMLPurifier_ConfigSchema_Interchange_Directive
{ {
/** /**
* ID of directive, instance of HTMLPurifier_ConfigSchema_Interchange_Id. * ID of directive.
* @type HTMLPurifier_ConfigSchema_Interchange_Id
*/ */
public $id; public $id;
/** /**
* String type, e.g. 'integer' or 'istring'. * Type, e.g. 'integer' or 'istring'.
* @type string
*/ */
public $type; public $type;
/** /**
* Default value, e.g. 3 or 'DefaultVal'. * Default value, e.g. 3 or 'DefaultVal'.
* @type mixed
*/ */
public $default; public $default;
/** /**
* HTML description. * HTML description.
* @type string
*/ */
public $description; public $description;
/** /**
* Boolean whether or not null is allowed as a value. * Whether or not null is allowed as a value.
* @type bool
*/ */
public $typeAllowsNull = false; public $typeAllowsNull = false;
/** /**
* Lookup table of allowed scalar values, e.g. array('allowed' => true). * Lookup table of allowed scalar values.
* e.g. array('allowed' => true).
* Null if all values are allowed. * Null if all values are allowed.
* @type array
*/ */
public $allowed; public $allowed;
/** /**
* List of aliases for the directive, * List of aliases for the directive.
* e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))). * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))).
* @type HTMLPurifier_ConfigSchema_Interchange_Id[]
*/ */
public $aliases = array(); public $aliases = array();
/** /**
* Hash of value aliases, e.g. array('alt' => 'real'). Null if value * Hash of value aliases, e.g. array('alt' => 'real'). Null if value
* aliasing is disabled (necessary for non-scalar types). * aliasing is disabled (necessary for non-scalar types).
* @type array
*/ */
public $valueAliases; public $valueAliases;
/** /**
* Version of HTML Purifier the directive was introduced, e.g. '1.3.1'. * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'.
* Null if the directive has always existed. * Null if the directive has always existed.
* @type string
*/ */
public $version; public $version;
/** /**
* ID of directive that supercedes this old directive, is an instance * ID of directive that supercedes this old directive.
* of HTMLPurifier_ConfigSchema_Interchange_Id. Null if not deprecated. * Null if not deprecated.
* @type HTMLPurifier_ConfigSchema_Interchange_Id
*/ */
public $deprecatedUse; public $deprecatedUse;
/** /**
* Version of HTML Purifier this directive was deprecated. Null if not * Version of HTML Purifier this directive was deprecated. Null if not
* deprecated. * deprecated.
* @type string
*/ */
public $deprecatedVersion; public $deprecatedVersion;
/** /**
* List of external projects this directive depends on, e.g. array('CSSTidy'). * List of external projects this directive depends on, e.g. array('CSSTidy').
* @type array
*/ */
public $external = array(); public $external = array();
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -6,32 +6,53 @@
class HTMLPurifier_ConfigSchema_Interchange_Id class HTMLPurifier_ConfigSchema_Interchange_Id
{ {
/**
* @type string
*/
public $key; public $key;
public function __construct($key) { /**
* @param string $key
*/
public function __construct($key)
{
$this->key = $key; $this->key = $key;
} }
/** /**
* @return string
* @warning This is NOT magic, to ensure that people don't abuse SPL and * @warning This is NOT magic, to ensure that people don't abuse SPL and
* cause problems for PHP 5.0 support. * cause problems for PHP 5.0 support.
*/ */
public function toString() { public function toString()
{
return $this->key; return $this->key;
} }
public function getRootNamespace() { /**
* @return string
*/
public function getRootNamespace()
{
return substr($this->key, 0, strpos($this->key, ".")); return substr($this->key, 0, strpos($this->key, "."));
} }
public function getDirective() { /**
* @return string
*/
public function getDirective()
{
return substr($this->key, strpos($this->key, ".") + 1); return substr($this->key, strpos($this->key, ".") + 1);
} }
public static function make($id) { /**
* @param string $id
* @return HTMLPurifier_ConfigSchema_Interchange_Id
*/
public static function make($id)
{
return new HTMLPurifier_ConfigSchema_Interchange_Id($id); return new HTMLPurifier_ConfigSchema_Interchange_Id($id);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -5,21 +5,39 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder
/** /**
* Used for processing DEFAULT, nothing else. * Used for processing DEFAULT, nothing else.
* @type HTMLPurifier_VarParser
*/ */
protected $varParser; protected $varParser;
public function __construct($varParser = null) { /**
* @param HTMLPurifier_VarParser $varParser
*/
public function __construct($varParser = null)
{
$this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); $this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native();
} }
public static function buildFromDirectory($dir = null) { /**
* @param string $dir
* @return HTMLPurifier_ConfigSchema_Interchange
*/
public static function buildFromDirectory($dir = null)
{
$builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
$interchange = new HTMLPurifier_ConfigSchema_Interchange(); $interchange = new HTMLPurifier_ConfigSchema_Interchange();
return $builder->buildDir($interchange, $dir); return $builder->buildDir($interchange, $dir);
} }
public function buildDir($interchange, $dir = null) { /**
if (!$dir) $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema'; * @param HTMLPurifier_ConfigSchema_Interchange $interchange
* @param string $dir
* @return HTMLPurifier_ConfigSchema_Interchange
*/
public function buildDir($interchange, $dir = null)
{
if (!$dir) {
$dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema';
}
if (file_exists($dir . '/info.ini')) { if (file_exists($dir . '/info.ini')) {
$info = parse_ini_file($dir . '/info.ini'); $info = parse_ini_file($dir . '/info.ini');
$interchange->name = $info['name']; $interchange->name = $info['name'];
@ -39,24 +57,30 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder
foreach ($files as $file) { foreach ($files as $file) {
$this->buildFile($interchange, $dir . '/' . $file); $this->buildFile($interchange, $dir . '/' . $file);
} }
return $interchange; return $interchange;
} }
public function buildFile($interchange, $file) { /**
* @param HTMLPurifier_ConfigSchema_Interchange $interchange
* @param string $file
*/
public function buildFile($interchange, $file)
{
$parser = new HTMLPurifier_StringHashParser(); $parser = new HTMLPurifier_StringHashParser();
$this->build( $this->build(
$interchange, $interchange,
new HTMLPurifier_StringHash( $parser->parseFile($file) ) new HTMLPurifier_StringHash($parser->parseFile($file))
); );
} }
/** /**
* Builds an interchange object based on a hash. * Builds an interchange object based on a hash.
* @param $interchange HTMLPurifier_ConfigSchema_Interchange object to build * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build
* @param $hash HTMLPurifier_ConfigSchema_StringHash source data * @param HTMLPurifier_StringHash $hash source data
* @throws HTMLPurifier_ConfigSchema_Exception
*/ */
public function build($interchange, $hash) { public function build($interchange, $hash)
{
if (!$hash instanceof HTMLPurifier_StringHash) { if (!$hash instanceof HTMLPurifier_StringHash) {
$hash = new HTMLPurifier_StringHash($hash); $hash = new HTMLPurifier_StringHash($hash);
} }
@ -75,7 +99,13 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder
$this->_findUnused($hash); $this->_findUnused($hash);
} }
public function buildDirective($interchange, $hash) { /**
* @param HTMLPurifier_ConfigSchema_Interchange $interchange
* @param HTMLPurifier_StringHash $hash
* @throws HTMLPurifier_ConfigSchema_Exception
*/
public function buildDirective($interchange, $hash)
{
$directive = new HTMLPurifier_ConfigSchema_Interchange_Directive(); $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive();
// These are required elements: // These are required elements:
@ -84,7 +114,9 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder
if (isset($hash['TYPE'])) { if (isset($hash['TYPE'])) {
$type = explode('/', $hash->offsetGet('TYPE')); $type = explode('/', $hash->offsetGet('TYPE'));
if (isset($type[1])) $directive->typeAllowsNull = true; if (isset($type[1])) {
$directive->typeAllowsNull = true;
}
$directive->type = $type[0]; $directive->type = $type[0];
} else { } else {
throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined");
@ -92,7 +124,11 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder
if (isset($hash['DEFAULT'])) { if (isset($hash['DEFAULT'])) {
try { try {
$directive->default = $this->varParser->parse($hash->offsetGet('DEFAULT'), $directive->type, $directive->typeAllowsNull); $directive->default = $this->varParser->parse(
$hash->offsetGet('DEFAULT'),
$directive->type,
$directive->typeAllowsNull
);
} catch (HTMLPurifier_VarParserException $e) { } catch (HTMLPurifier_VarParserException $e) {
throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'"); throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'");
} }
@ -139,34 +175,45 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder
/** /**
* Evaluates an array PHP code string without array() wrapper * Evaluates an array PHP code string without array() wrapper
* @param string $contents
*/ */
protected function evalArray($contents) { protected function evalArray($contents)
return eval('return array('. $contents .');'); {
return eval('return array(' . $contents . ');');
} }
/** /**
* Converts an array list into a lookup array. * Converts an array list into a lookup array.
* @param array $array
* @return array
*/ */
protected function lookup($array) { protected function lookup($array)
{
$ret = array(); $ret = array();
foreach ($array as $val) $ret[$val] = true; foreach ($array as $val) {
$ret[$val] = true;
}
return $ret; return $ret;
} }
/** /**
* Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id
* object based on a string Id. * object based on a string Id.
* @param string $id
* @return HTMLPurifier_ConfigSchema_Interchange_Id
*/ */
protected function id($id) { protected function id($id)
{
return HTMLPurifier_ConfigSchema_Interchange_Id::make($id); return HTMLPurifier_ConfigSchema_Interchange_Id::make($id);
} }
/** /**
* Triggers errors for any unused keys passed in the hash; such keys * Triggers errors for any unused keys passed in the hash; such keys
* may indicate typos, missing values, etc. * may indicate typos, missing values, etc.
* @param $hash Instance of ConfigSchema_StringHash to check. * @param HTMLPurifier_StringHash $hash Hash to check.
*/ */
protected function _findUnused($hash) { protected function _findUnused($hash)
{
$accessed = $hash->getAccessed(); $accessed = $hash->getAccessed();
foreach ($hash as $k => $v) { foreach ($hash as $k => $v) {
if (!isset($accessed[$k])) { if (!isset($accessed[$k])) {
@ -174,7 +221,6 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder
} }
} }
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -12,36 +12,48 @@ class HTMLPurifier_ConfigSchema_Validator
{ {
/** /**
* Easy to access global objects. * @type HTMLPurifier_ConfigSchema_Interchange
*/ */
protected $interchange, $aliases; protected $interchange;
/**
* @type array
*/
protected $aliases;
/** /**
* Context-stack to provide easy to read error messages. * Context-stack to provide easy to read error messages.
* @type array
*/ */
protected $context = array(); protected $context = array();
/** /**
* HTMLPurifier_VarParser to test default's type. * to test default's type.
* @type HTMLPurifier_VarParser
*/ */
protected $parser; protected $parser;
public function __construct() { public function __construct()
{
$this->parser = new HTMLPurifier_VarParser(); $this->parser = new HTMLPurifier_VarParser();
} }
/** /**
* Validates a fully-formed interchange object. Throws an * Validates a fully-formed interchange object.
* HTMLPurifier_ConfigSchema_Exception if there's a problem. * @param HTMLPurifier_ConfigSchema_Interchange $interchange
* @return bool
*/ */
public function validate($interchange) { public function validate($interchange)
{
$this->interchange = $interchange; $this->interchange = $interchange;
$this->aliases = array(); $this->aliases = array();
// PHP is a bit lax with integer <=> string conversions in // PHP is a bit lax with integer <=> string conversions in
// arrays, so we don't use the identical !== comparison // arrays, so we don't use the identical !== comparison
foreach ($interchange->directives as $i => $directive) { foreach ($interchange->directives as $i => $directive) {
$id = $directive->id->toString(); $id = $directive->id->toString();
if ($i != $id) $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'"); if ($i != $id) {
$this->error(false, "Integrity violation: key '$i' does not match internal id '$id'");
}
$this->validateDirective($directive); $this->validateDirective($directive);
} }
return true; return true;
@ -49,8 +61,10 @@ class HTMLPurifier_ConfigSchema_Validator
/** /**
* Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object.
* @param HTMLPurifier_ConfigSchema_Interchange_Id $id
*/ */
public function validateId($id) { public function validateId($id)
{
$id_string = $id->toString(); $id_string = $id->toString();
$this->context[] = "id '$id_string'"; $this->context[] = "id '$id_string'";
if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) {
@ -67,8 +81,10 @@ class HTMLPurifier_ConfigSchema_Validator
/** /**
* Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object.
* @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
*/ */
public function validateDirective($d) { public function validateDirective($d)
{
$id = $d->id->toString(); $id = $d->id->toString();
$this->context[] = "directive '$id'"; $this->context[] = "directive '$id'";
$this->validateId($d->id); $this->validateId($d->id);
@ -108,9 +124,13 @@ class HTMLPurifier_ConfigSchema_Validator
/** /**
* Extra validation if $allowed member variable of * Extra validation if $allowed member variable of
* HTMLPurifier_ConfigSchema_Interchange_Directive is defined. * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
* @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
*/ */
public function validateDirectiveAllowed($d) { public function validateDirectiveAllowed($d)
if (is_null($d->allowed)) return; {
if (is_null($d->allowed)) {
return;
}
$this->with($d, 'allowed') $this->with($d, 'allowed')
->assertNotEmpty() ->assertNotEmpty()
->assertIsLookup(); // handled by InterchangeBuilder ->assertIsLookup(); // handled by InterchangeBuilder
@ -119,7 +139,9 @@ class HTMLPurifier_ConfigSchema_Validator
} }
$this->context[] = 'allowed'; $this->context[] = 'allowed';
foreach ($d->allowed as $val => $x) { foreach ($d->allowed as $val => $x) {
if (!is_string($val)) $this->error("value $val", 'must be a string'); if (!is_string($val)) {
$this->error("value $val", 'must be a string');
}
} }
array_pop($this->context); array_pop($this->context);
} }
@ -127,15 +149,23 @@ class HTMLPurifier_ConfigSchema_Validator
/** /**
* Extra validation if $valueAliases member variable of * Extra validation if $valueAliases member variable of
* HTMLPurifier_ConfigSchema_Interchange_Directive is defined. * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
* @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
*/ */
public function validateDirectiveValueAliases($d) { public function validateDirectiveValueAliases($d)
if (is_null($d->valueAliases)) return; {
if (is_null($d->valueAliases)) {
return;
}
$this->with($d, 'valueAliases') $this->with($d, 'valueAliases')
->assertIsArray(); // handled by InterchangeBuilder ->assertIsArray(); // handled by InterchangeBuilder
$this->context[] = 'valueAliases'; $this->context[] = 'valueAliases';
foreach ($d->valueAliases as $alias => $real) { foreach ($d->valueAliases as $alias => $real) {
if (!is_string($alias)) $this->error("alias $alias", 'must be a string'); if (!is_string($alias)) {
if (!is_string($real)) $this->error("alias target $real from alias '$alias'", 'must be a string'); $this->error("alias $alias", 'must be a string');
}
if (!is_string($real)) {
$this->error("alias target $real from alias '$alias'", 'must be a string');
}
if ($alias === $real) { if ($alias === $real) {
$this->error("alias '$alias'", "must not be an alias to itself"); $this->error("alias '$alias'", "must not be an alias to itself");
} }
@ -155,8 +185,10 @@ class HTMLPurifier_ConfigSchema_Validator
/** /**
* Extra validation if $aliases member variable of * Extra validation if $aliases member variable of
* HTMLPurifier_ConfigSchema_Interchange_Directive is defined. * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
* @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
*/ */
public function validateDirectiveAliases($d) { public function validateDirectiveAliases($d)
{
$this->with($d, 'aliases') $this->with($d, 'aliases')
->assertIsArray(); // handled by InterchangeBuilder ->assertIsArray(); // handled by InterchangeBuilder
$this->context[] = 'aliases'; $this->context[] = 'aliases';
@ -180,27 +212,37 @@ class HTMLPurifier_ConfigSchema_Validator
/** /**
* Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom
* for validating simple member variables of objects. * for validating simple member variables of objects.
* @param $obj
* @param $member
* @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/ */
protected function with($obj, $member) { protected function with($obj, $member)
{
return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member); return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member);
} }
/** /**
* Emits an error, providing helpful context. * Emits an error, providing helpful context.
* @throws HTMLPurifier_ConfigSchema_Exception
*/ */
protected function error($target, $msg) { protected function error($target, $msg)
if ($target !== false) $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext(); {
else $prefix = ucfirst($this->getFormattedContext()); if ($target !== false) {
$prefix = ucfirst($target) . ' in ' . $this->getFormattedContext();
} else {
$prefix = ucfirst($this->getFormattedContext());
}
throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg));
} }
/** /**
* Returns a formatted context string. * Returns a formatted context string.
* @return string
*/ */
protected function getFormattedContext() { protected function getFormattedContext()
{
return implode(' in ', array_reverse($this->context)); return implode(' in ', array_reverse($this->context));
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -8,59 +8,123 @@
*/ */
class HTMLPurifier_ConfigSchema_ValidatorAtom class HTMLPurifier_ConfigSchema_ValidatorAtom
{ {
/**
* @type string
*/
protected $context;
protected $context, $obj, $member, $contents; /**
* @type object
*/
protected $obj;
public function __construct($context, $obj, $member) { /**
* @type string
*/
protected $member;
/**
* @type mixed
*/
protected $contents;
public function __construct($context, $obj, $member)
{
$this->context = $context; $this->context = $context;
$this->obj = $obj; $this->obj = $obj;
$this->member = $member; $this->member = $member;
$this->contents =& $obj->$member; $this->contents =& $obj->$member;
} }
public function assertIsString() { /**
if (!is_string($this->contents)) $this->error('must be a string'); * @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/
public function assertIsString()
{
if (!is_string($this->contents)) {
$this->error('must be a string');
}
return $this; return $this;
} }
public function assertIsBool() { /**
if (!is_bool($this->contents)) $this->error('must be a boolean'); * @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/
public function assertIsBool()
{
if (!is_bool($this->contents)) {
$this->error('must be a boolean');
}
return $this; return $this;
} }
public function assertIsArray() { /**
if (!is_array($this->contents)) $this->error('must be an array'); * @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/
public function assertIsArray()
{
if (!is_array($this->contents)) {
$this->error('must be an array');
}
return $this; return $this;
} }
public function assertNotNull() { /**
if ($this->contents === null) $this->error('must not be null'); * @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/
public function assertNotNull()
{
if ($this->contents === null) {
$this->error('must not be null');
}
return $this; return $this;
} }
public function assertAlnum() { /**
* @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/
public function assertAlnum()
{
$this->assertIsString(); $this->assertIsString();
if (!ctype_alnum($this->contents)) $this->error('must be alphanumeric'); if (!ctype_alnum($this->contents)) {
$this->error('must be alphanumeric');
}
return $this; return $this;
} }
public function assertNotEmpty() { /**
if (empty($this->contents)) $this->error('must not be empty'); * @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/
public function assertNotEmpty()
{
if (empty($this->contents)) {
$this->error('must not be empty');
}
return $this; return $this;
} }
public function assertIsLookup() { /**
* @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/
public function assertIsLookup()
{
$this->assertIsArray(); $this->assertIsArray();
foreach ($this->contents as $v) { foreach ($this->contents as $v) {
if ($v !== true) $this->error('must be a lookup array'); if ($v !== true) {
$this->error('must be a lookup array');
}
} }
return $this; return $this;
} }
protected function error($msg) { /**
* @param string $msg
* @throws HTMLPurifier_ConfigSchema_Exception
*/
protected function error($msg)
{
throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg); throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg);
} }
} }
// vim: et sw=4 sts=4 // vim: et sw=4 sts=4

View File

@ -0,0 +1,12 @@
CSS.AllowedFonts
TYPE: lookup/null
VERSION: 4.3.0
DEFAULT: NULL
--DESCRIPTION--
<p>
Allows you to manually specify a set of allowed fonts. If
<code>NULL</code>, all fonts are allowed. This directive
affects generic names (serif, sans-serif, monospace, cursive,
fantasy) as well as specific font families.
</p>
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,13 @@
CSS.ForbiddenProperties
TYPE: lookup
VERSION: 4.2.0
DEFAULT: array()
--DESCRIPTION--
<p>
This is the logical inverse of %CSS.AllowedProperties, and it will
override that directive or any other directive. If possible,
%CSS.AllowedProperties is recommended over this directive,
because it can sometimes be difficult to tell whether or not you've
forbidden all of the CSS properties you truly would like to disallow.
</p>
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,9 @@
CSS.Trusted
TYPE: bool
VERSION: 4.2.1
DEFAULT: false
--DESCRIPTION--
Indicates whether or not the user's CSS input is trusted or not. If the
input is trusted, a more expansive set of allowed properties. See
also %HTML.Trusted.
--# vim: et sw=4 sts=4

Some files were not shown because too many files have changed in this diff Show More