@@ -1,5 +1,6 @@ | |||
<?php | |||
if (!defined('DEDEINC')) exit ('dedebiz'); | |||
require_once DEDEINC."/libraries/HTMLPurifier/HTMLPurifier.auto.php"; | |||
/** | |||
* 过滤助手 | |||
* | |||
@@ -27,24 +28,36 @@ if (!function_exists('HtmlReplace')) { | |||
if (!is_string($str)) { | |||
return ''; | |||
} | |||
$str = stripslashes($str); | |||
$str = preg_replace("/<[\/]{0,1}style([^>]*)>(.*)<\/style>/i", '', $str); | |||
$str = stripslashes($str); // 取消转义 | |||
// 初始化 HTMLPurifier 配置(静态变量优化性能) | |||
static $purifier = null; | |||
$config = HTMLPurifier_Config::createDefault(); | |||
$config->set('HTML.Allowed', ''); // 只保留文本 | |||
if ($purifier === null) { | |||
$config->set('Cache.SerializerPath', DEDEDATA.'/cache'); | |||
$purifier = new HTMLPurifier($config); | |||
} | |||
// 处理不同模式 | |||
if ($rptype == 0) { | |||
$str = dede_htmlspecialchars($str); | |||
} else if ($rptype == 1) { | |||
$str = dede_htmlspecialchars($str); | |||
$str = str_replace(" ", ' ', $str); | |||
$str = preg_replace("/[\r\n\t ]{1,}/", ' ', $str); | |||
} else if ($rptype == 2) { | |||
$str = dede_htmlspecialchars($str); | |||
$str = str_replace(" ", '', $str); | |||
$str = preg_replace("/[\r\n\t ]/", '', $str); | |||
// 仅替换 HTML 标记 | |||
$str = $purifier->purify($str); | |||
} elseif ($rptype == 1) { | |||
// 替换 HTML 标记 + 去除连续空白字符 | |||
$str = $purifier->purify($str); | |||
$str = preg_replace("/[\r\n\t ]+/", ' ', $str); // 合并多余空格 | |||
} elseif ($rptype == 2) { | |||
// 替换 HTML 标记 + 去除所有空白字符 | |||
$str = $purifier->purify($str); | |||
$str = preg_replace("/\s+/", '', $str); | |||
} else { | |||
$str = preg_replace("/[\r\n\t ]{1,}/", ' ', $str); | |||
$str = preg_replace('/script/i', 'script', $str); | |||
$str = preg_replace("/<[\/]{0,1}(link|meta|iframe|frame|object|embed|form|input|button|textarea|select)[^>]*>/i", '', $str); | |||
$str = preg_replace('/\son\w+\s*=\s*["\'][^"\']*["\']/i', '', $str); | |||
// 仅替换 HTML 危险标记 | |||
$config->set('HTML.ForbiddenElements', ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'textarea', 'select', 'meta', 'link']); | |||
$str = $purifier->purify($str); | |||
} | |||
return addslashes($str); | |||
} | |||
} | |||
@@ -57,42 +70,25 @@ if (!function_exists('HtmlReplace')) { | |||
if (!function_exists('RemoveXSS')) { | |||
function RemoveXSS($val) | |||
{ | |||
$val = preg_replace('/([\x00-\x08|\x0b-\x0c|\x0e-\x19])/', '', $val); | |||
$search = 'abcdefghijklmnopqrstuvwxyz'; | |||
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; | |||
$search .= '1234567890!@#$%^&*()'; | |||
$search .= '~`";:?+/={}[]-_|\'\\'; | |||
for ($i = 0; $i < strlen($search); $i++) { | |||
$val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); //with a ; | |||
$val = preg_replace('/(�{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); //with a ; | |||
} | |||
$ra1 = array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base'); | |||
$ra2 = array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload'); | |||
$ra = array_merge($ra1, $ra2); | |||
$found = true; | |||
while ($found == true) { | |||
$val_before = $val; | |||
for ($i = 0; $i < sizeof($ra); $i++) { | |||
$pattern = '/'; | |||
for ($j = 0; $j < strlen($ra[$i]); $j++) { | |||
if ($j > 0) { | |||
$pattern .= '('; | |||
$pattern .= '(&#[xX]0{0,8}([9ab]);)'; | |||
$pattern .= '|'; | |||
$pattern .= '|(�{0,8}([9|10|13]);)'; | |||
$pattern .= ')*'; | |||
} | |||
$pattern .= $ra[$i][$j]; | |||
} | |||
$pattern .= '/i'; | |||
$replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); | |||
$val = preg_replace($pattern, $replacement, $val); | |||
if ($val_before == $val) { | |||
$found = false; | |||
} | |||
} | |||
static $purifier = null; | |||
if ($purifier === null) { | |||
$config = HTMLPurifier_Config::createDefault(); | |||
// 启用缓存(提升性能) | |||
$config->set('Cache.SerializerPath', DEDEDATA.'/cache'); // 生产环境建议设定缓存目录 | |||
// 允许的 HTML 元素(可以根据需要调整) | |||
$config->set('HTML.Allowed', 'p,b,strong,i,em,u,a[href|title],ul,ol,li,img[src|alt|width|height],br,span[class]'); | |||
// 过滤 JavaScript、CSS 注入 | |||
$config->set('CSS.AllowedProperties', []); | |||
$config->set('URI.DisableExternalResources', true); | |||
$config->set('URI.DisableResources', true); | |||
$purifier = new HTMLPurifier($config); | |||
} | |||
return $val; | |||
return $purifier->purify($val); | |||
} | |||
} | |||
/** | |||
@@ -1,26 +1,30 @@ | |||
<?php | |||
if (!defined('DEDEINC')) exit ('dedebiz'); | |||
function SpHtml2Text($str) | |||
require_once DEDEINC."/libraries/HTMLPurifier/HTMLPurifier.auto.php"; | |||
function SpHtml2Text($html) | |||
{ | |||
$str = preg_replace("/<sty(.*)\\/style>|<scr(.*)\\/script>|<!--(.*)-->/isU","",$str); | |||
$alltext = ''; | |||
$start = 1; | |||
for ($i = 0; $i < strlen($str); $i++) | |||
{ | |||
if ($start == 0 && $str[$i] == ">") { | |||
$start = 1; | |||
} else if ($start==1) { | |||
if ($str[$i]=="<") { | |||
$start = 0; | |||
$alltext .= " "; | |||
} else if (ord($str[$i]) > 31) { | |||
$alltext .= $str[$i]; | |||
} | |||
} | |||
} | |||
$alltext = str_replace(" "," ",$alltext); | |||
$alltext = preg_replace("/&([^;&]*)(;|&)/","",$alltext); | |||
$alltext = preg_replace("/[ ]+/s"," ",$alltext); | |||
return $alltext; | |||
// 初始化 HTMLPurifier 配置 | |||
static $purifier = null; | |||
if ($purifier === null) { | |||
$config = HTMLPurifier_Config::createDefault(); | |||
// 禁止所有 HTML 标签,只允许文本 | |||
$config->set('HTML.Allowed', ''); | |||
// 配置缓存 | |||
$cacheDir = DEDEDATA.'/cache'; | |||
$config->set('Cache.SerializerPath', $cacheDir); | |||
$purifier = new HTMLPurifier($config); | |||
} | |||
// 过滤掉所有 HTML,只保留纯文本 | |||
$cleanText = $purifier->purify($html); | |||
// 进一步去除可能的额外空格和换行符 | |||
$cleanText = trim($cleanText); | |||
$cleanText = preg_replace("/[\r\n\t ]+/", ' ', $cleanText); | |||
return $cleanText; | |||
} | |||
?> |
@@ -0,0 +1,11 @@ | |||
<?php | |||
/** | |||
* This is a stub include that automatically configures the include path. | |||
*/ | |||
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() ); | |||
require_once 'HTMLPurifier/Bootstrap.php'; | |||
require_once 'HTMLPurifier.autoload.php'; | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,14 @@ | |||
<?php | |||
/** | |||
* @file | |||
* Legacy autoloader for systems lacking spl_autoload_register | |||
* | |||
*/ | |||
spl_autoload_register(function($class) | |||
{ | |||
return HTMLPurifier_Bootstrap::autoload($class); | |||
}); | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,25 @@ | |||
<?php | |||
/** | |||
* @file | |||
* 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')) { | |||
// We need unregister for our pre-registering functionality | |||
HTMLPurifier_Bootstrap::registerAutoload(); | |||
if (function_exists('__autoload')) { | |||
// Be polite and ensure that userland autoload gets retained | |||
spl_autoload_register('__autoload'); | |||
} | |||
} elseif (!function_exists('__autoload')) { | |||
require dirname(__FILE__) . '/HTMLPurifier.autoload-legacy.php'; | |||
} | |||
// phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.zend_ze1_compatibility_modeRemoved | |||
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 |
@@ -0,0 +1,4 @@ | |||
<?php | |||
if (!defined('HTMLPURIFIER_PREFIX')) { | |||
define('HTMLPURIFIER_PREFIX', dirname(__FILE__)); | |||
} |
@@ -0,0 +1,25 @@ | |||
<?php | |||
/** | |||
* @file | |||
* Defines a function wrapper for HTML Purifier for quick use. | |||
* @note ''HTMLPurifier()'' is NOT the same as ''new HTMLPurifier()'' | |||
*/ | |||
/** | |||
* Purify HTML. | |||
* @param string $html String HTML to purify | |||
* @param mixed $config Configuration to use, can be any value accepted by | |||
* HTMLPurifier_Config::create() | |||
* @return string | |||
*/ | |||
function HTMLPurifier($html, $config = null) | |||
{ | |||
static $purifier = false; | |||
if (!$purifier) { | |||
$purifier = new HTMLPurifier(); | |||
} | |||
return $purifier->purify($html, $config); | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,235 @@ | |||
<?php | |||
/** | |||
* @file | |||
* This file was auto-generated by generate-includes.php and includes all of | |||
* the core files required by HTML Purifier. Use this if performance is a | |||
* 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. | |||
* | |||
* @version 4.15.0 | |||
* | |||
* @warning | |||
* You must *not* include any other HTML Purifier files before this file, | |||
* because 'require' not 'require_once' is used. | |||
* | |||
* @warning | |||
* This file requires that the include path contains the HTML Purifier | |||
* library directory; this is not auto-set. | |||
*/ | |||
require 'HTMLPurifier.php'; | |||
require 'HTMLPurifier/Arborize.php'; | |||
require 'HTMLPurifier/AttrCollections.php'; | |||
require 'HTMLPurifier/AttrDef.php'; | |||
require 'HTMLPurifier/AttrTransform.php'; | |||
require 'HTMLPurifier/AttrTypes.php'; | |||
require 'HTMLPurifier/AttrValidator.php'; | |||
require 'HTMLPurifier/Bootstrap.php'; | |||
require 'HTMLPurifier/Definition.php'; | |||
require 'HTMLPurifier/CSSDefinition.php'; | |||
require 'HTMLPurifier/ChildDef.php'; | |||
require 'HTMLPurifier/Config.php'; | |||
require 'HTMLPurifier/ConfigSchema.php'; | |||
require 'HTMLPurifier/ContentSets.php'; | |||
require 'HTMLPurifier/Context.php'; | |||
require 'HTMLPurifier/DefinitionCache.php'; | |||
require 'HTMLPurifier/DefinitionCacheFactory.php'; | |||
require 'HTMLPurifier/Doctype.php'; | |||
require 'HTMLPurifier/DoctypeRegistry.php'; | |||
require 'HTMLPurifier/ElementDef.php'; | |||
require 'HTMLPurifier/Encoder.php'; | |||
require 'HTMLPurifier/EntityLookup.php'; | |||
require 'HTMLPurifier/EntityParser.php'; | |||
require 'HTMLPurifier/ErrorCollector.php'; | |||
require 'HTMLPurifier/ErrorStruct.php'; | |||
require 'HTMLPurifier/Exception.php'; | |||
require 'HTMLPurifier/Filter.php'; | |||
require 'HTMLPurifier/Generator.php'; | |||
require 'HTMLPurifier/HTMLDefinition.php'; | |||
require 'HTMLPurifier/HTMLModule.php'; | |||
require 'HTMLPurifier/HTMLModuleManager.php'; | |||
require 'HTMLPurifier/IDAccumulator.php'; | |||
require 'HTMLPurifier/Injector.php'; | |||
require 'HTMLPurifier/Language.php'; | |||
require 'HTMLPurifier/LanguageFactory.php'; | |||
require 'HTMLPurifier/Length.php'; | |||
require 'HTMLPurifier/Lexer.php'; | |||
require 'HTMLPurifier/Node.php'; | |||
require 'HTMLPurifier/PercentEncoder.php'; | |||
require 'HTMLPurifier/PropertyList.php'; | |||
require 'HTMLPurifier/PropertyListIterator.php'; | |||
require 'HTMLPurifier/Queue.php'; | |||
require 'HTMLPurifier/Strategy.php'; | |||
require 'HTMLPurifier/StringHash.php'; | |||
require 'HTMLPurifier/StringHashParser.php'; | |||
require 'HTMLPurifier/TagTransform.php'; | |||
require 'HTMLPurifier/Token.php'; | |||
require 'HTMLPurifier/TokenFactory.php'; | |||
require 'HTMLPurifier/URI.php'; | |||
require 'HTMLPurifier/URIDefinition.php'; | |||
require 'HTMLPurifier/URIFilter.php'; | |||
require 'HTMLPurifier/URIParser.php'; | |||
require 'HTMLPurifier/URIScheme.php'; | |||
require 'HTMLPurifier/URISchemeRegistry.php'; | |||
require 'HTMLPurifier/UnitConverter.php'; | |||
require 'HTMLPurifier/VarParser.php'; | |||
require 'HTMLPurifier/VarParserException.php'; | |||
require 'HTMLPurifier/Zipper.php'; | |||
require 'HTMLPurifier/AttrDef/CSS.php'; | |||
require 'HTMLPurifier/AttrDef/Clone.php'; | |||
require 'HTMLPurifier/AttrDef/Enum.php'; | |||
require 'HTMLPurifier/AttrDef/Integer.php'; | |||
require 'HTMLPurifier/AttrDef/Lang.php'; | |||
require 'HTMLPurifier/AttrDef/Switch.php'; | |||
require 'HTMLPurifier/AttrDef/Text.php'; | |||
require 'HTMLPurifier/AttrDef/URI.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Number.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/AlphaValue.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Background.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/BackgroundPosition.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Border.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Color.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Composite.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Filter.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Font.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/FontFamily.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Ident.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/ImportantDecorator.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Length.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/ListStyle.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Multiple.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/Percentage.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/TextDecoration.php'; | |||
require 'HTMLPurifier/AttrDef/CSS/URI.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/Bool.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/Nmtokens.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/Class.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/Color.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/ContentEditable.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/FrameTarget.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/ID.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/Pixels.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/Length.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/LinkTypes.php'; | |||
require 'HTMLPurifier/AttrDef/HTML/MultiLength.php'; | |||
require 'HTMLPurifier/AttrDef/URI/Email.php'; | |||
require 'HTMLPurifier/AttrDef/URI/Host.php'; | |||
require 'HTMLPurifier/AttrDef/URI/IPv4.php'; | |||
require 'HTMLPurifier/AttrDef/URI/IPv6.php'; | |||
require 'HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php'; | |||
require 'HTMLPurifier/AttrTransform/Background.php'; | |||
require 'HTMLPurifier/AttrTransform/BdoDir.php'; | |||
require 'HTMLPurifier/AttrTransform/BgColor.php'; | |||
require 'HTMLPurifier/AttrTransform/BoolToCSS.php'; | |||
require 'HTMLPurifier/AttrTransform/Border.php'; | |||
require 'HTMLPurifier/AttrTransform/EnumToCSS.php'; | |||
require 'HTMLPurifier/AttrTransform/ImgRequired.php'; | |||
require 'HTMLPurifier/AttrTransform/ImgSpace.php'; | |||
require 'HTMLPurifier/AttrTransform/Input.php'; | |||
require 'HTMLPurifier/AttrTransform/Lang.php'; | |||
require 'HTMLPurifier/AttrTransform/Length.php'; | |||
require 'HTMLPurifier/AttrTransform/Name.php'; | |||
require 'HTMLPurifier/AttrTransform/NameSync.php'; | |||
require 'HTMLPurifier/AttrTransform/Nofollow.php'; | |||
require 'HTMLPurifier/AttrTransform/SafeEmbed.php'; | |||
require 'HTMLPurifier/AttrTransform/SafeObject.php'; | |||
require 'HTMLPurifier/AttrTransform/SafeParam.php'; | |||
require 'HTMLPurifier/AttrTransform/ScriptRequired.php'; | |||
require 'HTMLPurifier/AttrTransform/TargetBlank.php'; | |||
require 'HTMLPurifier/AttrTransform/TargetNoopener.php'; | |||
require 'HTMLPurifier/AttrTransform/TargetNoreferrer.php'; | |||
require 'HTMLPurifier/AttrTransform/Textarea.php'; | |||
require 'HTMLPurifier/ChildDef/Chameleon.php'; | |||
require 'HTMLPurifier/ChildDef/Custom.php'; | |||
require 'HTMLPurifier/ChildDef/Empty.php'; | |||
require 'HTMLPurifier/ChildDef/List.php'; | |||
require 'HTMLPurifier/ChildDef/Required.php'; | |||
require 'HTMLPurifier/ChildDef/Optional.php'; | |||
require 'HTMLPurifier/ChildDef/StrictBlockquote.php'; | |||
require 'HTMLPurifier/ChildDef/Table.php'; | |||
require 'HTMLPurifier/DefinitionCache/Decorator.php'; | |||
require 'HTMLPurifier/DefinitionCache/Null.php'; | |||
require 'HTMLPurifier/DefinitionCache/Serializer.php'; | |||
require 'HTMLPurifier/DefinitionCache/Decorator/Cleanup.php'; | |||
require 'HTMLPurifier/DefinitionCache/Decorator/Memory.php'; | |||
require 'HTMLPurifier/HTMLModule/Bdo.php'; | |||
require 'HTMLPurifier/HTMLModule/CommonAttributes.php'; | |||
require 'HTMLPurifier/HTMLModule/Edit.php'; | |||
require 'HTMLPurifier/HTMLModule/Forms.php'; | |||
require 'HTMLPurifier/HTMLModule/Hypertext.php'; | |||
require 'HTMLPurifier/HTMLModule/Iframe.php'; | |||
require 'HTMLPurifier/HTMLModule/Image.php'; | |||
require 'HTMLPurifier/HTMLModule/Legacy.php'; | |||
require 'HTMLPurifier/HTMLModule/List.php'; | |||
require 'HTMLPurifier/HTMLModule/Name.php'; | |||
require 'HTMLPurifier/HTMLModule/Nofollow.php'; | |||
require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php'; | |||
require 'HTMLPurifier/HTMLModule/Object.php'; | |||
require 'HTMLPurifier/HTMLModule/Presentation.php'; | |||
require 'HTMLPurifier/HTMLModule/Proprietary.php'; | |||
require 'HTMLPurifier/HTMLModule/Ruby.php'; | |||
require 'HTMLPurifier/HTMLModule/SafeEmbed.php'; | |||
require 'HTMLPurifier/HTMLModule/SafeObject.php'; | |||
require 'HTMLPurifier/HTMLModule/SafeScripting.php'; | |||
require 'HTMLPurifier/HTMLModule/Scripting.php'; | |||
require 'HTMLPurifier/HTMLModule/StyleAttribute.php'; | |||
require 'HTMLPurifier/HTMLModule/Tables.php'; | |||
require 'HTMLPurifier/HTMLModule/Target.php'; | |||
require 'HTMLPurifier/HTMLModule/TargetBlank.php'; | |||
require 'HTMLPurifier/HTMLModule/TargetNoopener.php'; | |||
require 'HTMLPurifier/HTMLModule/TargetNoreferrer.php'; | |||
require 'HTMLPurifier/HTMLModule/Text.php'; | |||
require 'HTMLPurifier/HTMLModule/Tidy.php'; | |||
require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php'; | |||
require 'HTMLPurifier/HTMLModule/Tidy/Name.php'; | |||
require 'HTMLPurifier/HTMLModule/Tidy/Proprietary.php'; | |||
require 'HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php'; | |||
require 'HTMLPurifier/HTMLModule/Tidy/Strict.php'; | |||
require 'HTMLPurifier/HTMLModule/Tidy/Transitional.php'; | |||
require 'HTMLPurifier/HTMLModule/Tidy/XHTML.php'; | |||
require 'HTMLPurifier/Injector/AutoParagraph.php'; | |||
require 'HTMLPurifier/Injector/DisplayLinkURI.php'; | |||
require 'HTMLPurifier/Injector/Linkify.php'; | |||
require 'HTMLPurifier/Injector/PurifierLinkify.php'; | |||
require 'HTMLPurifier/Injector/RemoveEmpty.php'; | |||
require 'HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php'; | |||
require 'HTMLPurifier/Injector/SafeObject.php'; | |||
require 'HTMLPurifier/Lexer/DOMLex.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/Core.php'; | |||
require 'HTMLPurifier/Strategy/FixNesting.php'; | |||
require 'HTMLPurifier/Strategy/MakeWellFormed.php'; | |||
require 'HTMLPurifier/Strategy/RemoveForeignElements.php'; | |||
require 'HTMLPurifier/Strategy/ValidateAttributes.php'; | |||
require 'HTMLPurifier/TagTransform/Font.php'; | |||
require 'HTMLPurifier/TagTransform/Simple.php'; | |||
require 'HTMLPurifier/Token/Comment.php'; | |||
require 'HTMLPurifier/Token/Tag.php'; | |||
require 'HTMLPurifier/Token/Empty.php'; | |||
require 'HTMLPurifier/Token/End.php'; | |||
require 'HTMLPurifier/Token/Start.php'; | |||
require 'HTMLPurifier/Token/Text.php'; | |||
require 'HTMLPurifier/URIFilter/DisableExternal.php'; | |||
require 'HTMLPurifier/URIFilter/DisableExternalResources.php'; | |||
require 'HTMLPurifier/URIFilter/DisableResources.php'; | |||
require 'HTMLPurifier/URIFilter/HostBlacklist.php'; | |||
require 'HTMLPurifier/URIFilter/MakeAbsolute.php'; | |||
require 'HTMLPurifier/URIFilter/Munge.php'; | |||
require 'HTMLPurifier/URIFilter/SafeIframe.php'; | |||
require 'HTMLPurifier/URIScheme/data.php'; | |||
require 'HTMLPurifier/URIScheme/file.php'; | |||
require 'HTMLPurifier/URIScheme/ftp.php'; | |||
require 'HTMLPurifier/URIScheme/http.php'; | |||
require 'HTMLPurifier/URIScheme/https.php'; | |||
require 'HTMLPurifier/URIScheme/mailto.php'; | |||
require 'HTMLPurifier/URIScheme/news.php'; | |||
require 'HTMLPurifier/URIScheme/nntp.php'; | |||
require 'HTMLPurifier/URIScheme/tel.php'; | |||
require 'HTMLPurifier/VarParser/Flexible.php'; | |||
require 'HTMLPurifier/VarParser/Native.php'; |
@@ -0,0 +1,30 @@ | |||
<?php | |||
/** | |||
* @file | |||
* Emulation layer for code that used kses(), substituting in HTML Purifier. | |||
*/ | |||
require_once dirname(__FILE__) . '/HTMLPurifier.auto.php'; | |||
function kses($string, $allowed_html, $allowed_protocols = null) | |||
{ | |||
$config = HTMLPurifier_Config::createDefault(); | |||
$allowed_elements = array(); | |||
$allowed_attributes = array(); | |||
foreach ($allowed_html as $element => $attributes) { | |||
$allowed_elements[$element] = true; | |||
foreach ($attributes as $attribute => $x) { | |||
$allowed_attributes["$element.$attribute"] = true; | |||
} | |||
} | |||
$config->set('HTML.AllowedElements', $allowed_elements); | |||
$config->set('HTML.AllowedAttributes', $allowed_attributes); | |||
if ($allowed_protocols !== null) { | |||
$config->set('URI.AllowedSchemes', $allowed_protocols); | |||
} | |||
$purifier = new HTMLPurifier($config); | |||
return $purifier->purify($string); | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,11 @@ | |||
<?php | |||
/** | |||
* @file | |||
* Convenience stub file that adds HTML Purifier's library file to the path | |||
* without any other side-effects. | |||
*/ | |||
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() ); | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,297 @@ | |||
<?php | |||
/*! @mainpage | |||
* | |||
* HTML Purifier is an HTML filter that will take an arbitrary snippet of | |||
* HTML and rigorously test, validate and filter it into a version that | |||
* is safe for output onto webpages. It achieves this by: | |||
* | |||
* -# Lexing (parsing into tokens) the document, | |||
* -# Executing various strategies on the tokens: | |||
* -# Removing all elements not in the whitelist, | |||
* -# Making the tokens well-formed, | |||
* -# Fixing the nesting of the nodes, and | |||
* -# Validating attributes of the nodes; and | |||
* -# Generating HTML from the purified tokens. | |||
* | |||
* However, most users will only need to interface with the HTMLPurifier | |||
* and HTMLPurifier_Config. | |||
*/ | |||
/* | |||
HTML Purifier 4.15.0 - Standards Compliant HTML Filtering | |||
Copyright (C) 2006-2008 Edward Z. Yang | |||
This library is free software; you can redistribute it and/or | |||
modify it under the terms of the GNU Lesser General Public | |||
License as published by the Free Software Foundation; either | |||
version 2.1 of the License, or (at your option) any later version. | |||
This library is distributed in the hope that it will be useful, | |||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
Lesser General Public License for more details. | |||
You should have received a copy of the GNU Lesser General Public | |||
License along with this library; if not, write to the Free Software | |||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
*/ | |||
/** | |||
* Facade that coordinates HTML Purifier's subsystems in order to purify HTML. | |||
* | |||
* @note There are several points in which configuration can be specified | |||
* for HTML Purifier. The precedence of these (from lowest to | |||
* highest) is as follows: | |||
* -# Instance: new HTMLPurifier($config) | |||
* -# Invocation: purify($html, $config) | |||
* These configurations are entirely independent of each other and | |||
* are *not* merged (this behavior may change in the future). | |||
* | |||
* @todo We need an easier way to inject strategies using the configuration | |||
* object. | |||
*/ | |||
class HTMLPurifier | |||
{ | |||
/** | |||
* Version of HTML Purifier. | |||
* @type string | |||
*/ | |||
public $version = '4.15.0'; | |||
/** | |||
* Constant with version of HTML Purifier. | |||
*/ | |||
const VERSION = '4.15.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; | |||
/** | |||
* Initializes the purifier. | |||
* | |||
* @param HTMLPurifier_Config|mixed $config Optional HTMLPurifier_Config object | |||
* 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 | |||
* HTMLPurifier_Config::create() supports. | |||
*/ | |||
public function __construct($config = null) | |||
{ | |||
$this->config = HTMLPurifier_Config::create($config); | |||
$this->strategy = new HTMLPurifier_Strategy_Core(); | |||
} | |||
/** | |||
* Adds a filter to process the output. First come first serve | |||
* | |||
* @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object | |||
*/ | |||
public function addFilter($filter) | |||
{ | |||
trigger_error( | |||
'HTMLPurifier->addFilter() is deprecated, use configuration directives' . | |||
' in the Filter namespace or Filter.Custom', | |||
E_USER_WARNING | |||
); | |||
$this->filters[] = $filter; | |||
} | |||
/** | |||
* Filters an HTML snippet/document to be XSS-free and standards-compliant. | |||
* | |||
* @param string $html String of HTML to purify | |||
* @param HTMLPurifier_Config $config Config object for this operation, | |||
* if omitted, defaults to the config object specified during this | |||
* object's construction. The parameter can also be any type | |||
* that HTMLPurifier_Config::create() supports. | |||
* | |||
* @return string Purified HTML | |||
*/ | |||
public function purify($html, $config = null) | |||
{ | |||
// :TODO: make the config merge in, instead of replace | |||
$config = $config ? HTMLPurifier_Config::create($config) : $this->config; | |||
// implementation is partially environment dependant, partially | |||
// configuration dependant | |||
$lexer = HTMLPurifier_Lexer::create($config); | |||
$context = new HTMLPurifier_Context(); | |||
// setup HTML generator | |||
$this->generator = new HTMLPurifier_Generator($config, $context); | |||
$context->register('Generator', $this->generator); | |||
// set up global context variables | |||
if ($config->get('Core.CollectErrors')) { | |||
// may get moved out if other facilities use it | |||
$language_factory = HTMLPurifier_LanguageFactory::instance(); | |||
$language = $language_factory->create($config, $context); | |||
$context->register('Locale', $language); | |||
$error_collector = new HTMLPurifier_ErrorCollector($context); | |||
$context->register('ErrorCollector', $error_collector); | |||
} | |||
// setup id_accumulator context, necessary due to the fact that | |||
// AttrValidator can be called from many places | |||
$id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); | |||
$context->register('IDAccumulator', $id_accumulator); | |||
$html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); | |||
// setup filters | |||
$filter_flags = $config->getBatch('Filter'); | |||
$custom_filters = $filter_flags['Custom']; | |||
unset($filter_flags['Custom']); | |||
$filters = array(); | |||
foreach ($filter_flags as $filter => $flag) { | |||
if (!$flag) { | |||
continue; | |||
} | |||
if (strpos($filter, '.') !== false) { | |||
continue; | |||
} | |||
$class = "HTMLPurifier_Filter_$filter"; | |||
$filters[] = new $class; | |||
} | |||
foreach ($custom_filters as $filter) { | |||
// maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat | |||
$filters[] = $filter; | |||
} | |||
$filters = array_merge($filters, $this->filters); | |||
// maybe prepare(), but later | |||
for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) { | |||
$html = $filters[$i]->preFilter($html, $config, $context); | |||
} | |||
// purified HTML | |||
$html = | |||
$this->generator->generateFromTokens( | |||
// list of tokens | |||
$this->strategy->execute( | |||
// list of un-purified tokens | |||
$lexer->tokenizeHTML( | |||
// un-purified HTML | |||
$html, | |||
$config, | |||
$context | |||
), | |||
$config, | |||
$context | |||
) | |||
); | |||
for ($i = $filter_size - 1; $i >= 0; $i--) { | |||
$html = $filters[$i]->postFilter($html, $config, $context); | |||
} | |||
$html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); | |||
$this->context =& $context; | |||
return $html; | |||
} | |||
/** | |||
* Filters an array of HTML snippets | |||
* | |||
* @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. | |||
* | |||
* @return string[] Array of purified HTML | |||
*/ | |||
public function purifyArray($array_of_html, $config = null) | |||
{ | |||
$context_array = array(); | |||
$array = array(); | |||
foreach($array_of_html as $key=>$value){ | |||
if (is_array($value)) { | |||
$array[$key] = $this->purifyArray($value, $config); | |||
} else { | |||
$array[$key] = $this->purify($value, $config); | |||
} | |||
$context_array[$key] = $this->context; | |||
} | |||
$this->context = $context_array; | |||
return $array; | |||
} | |||
/** | |||
* 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 | |||
*/ | |||
public static function instance($prototype = null) | |||
{ | |||
if (!self::$instance || $prototype) { | |||
if ($prototype instanceof HTMLPurifier) { | |||
self::$instance = $prototype; | |||
} elseif ($prototype) { | |||
self::$instance = new HTMLPurifier($prototype); | |||
} else { | |||
self::$instance = new HTMLPurifier(); | |||
} | |||
} | |||
return self::$instance; | |||
} | |||
/** | |||
* 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() | |||
*/ | |||
public static function getInstance($prototype = null) | |||
{ | |||
return HTMLPurifier::instance($prototype); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,229 @@ | |||
<?php | |||
/** | |||
* @file | |||
* This file was auto-generated by generate-includes.php and includes all of | |||
* the core files required by HTML Purifier. This is a convenience stub that | |||
* includes all files using dirname(__FILE__) and require_once. PLEASE DO NOT | |||
* EDIT THIS FILE, changes will be overwritten the next time the script is run. | |||
* | |||
* Changes to include_path are not necessary. | |||
*/ | |||
$__dir = dirname(__FILE__); | |||
require_once $__dir . '/HTMLPurifier.php'; | |||
require_once $__dir . '/HTMLPurifier/Arborize.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrCollections.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTypes.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrValidator.php'; | |||
require_once $__dir . '/HTMLPurifier/Bootstrap.php'; | |||
require_once $__dir . '/HTMLPurifier/Definition.php'; | |||
require_once $__dir . '/HTMLPurifier/CSSDefinition.php'; | |||
require_once $__dir . '/HTMLPurifier/ChildDef.php'; | |||
require_once $__dir . '/HTMLPurifier/Config.php'; | |||
require_once $__dir . '/HTMLPurifier/ConfigSchema.php'; | |||
require_once $__dir . '/HTMLPurifier/ContentSets.php'; | |||
require_once $__dir . '/HTMLPurifier/Context.php'; | |||
require_once $__dir . '/HTMLPurifier/DefinitionCache.php'; | |||
require_once $__dir . '/HTMLPurifier/DefinitionCacheFactory.php'; | |||
require_once $__dir . '/HTMLPurifier/Doctype.php'; | |||
require_once $__dir . '/HTMLPurifier/DoctypeRegistry.php'; | |||
require_once $__dir . '/HTMLPurifier/ElementDef.php'; | |||
require_once $__dir . '/HTMLPurifier/Encoder.php'; | |||
require_once $__dir . '/HTMLPurifier/EntityLookup.php'; | |||
require_once $__dir . '/HTMLPurifier/EntityParser.php'; | |||
require_once $__dir . '/HTMLPurifier/ErrorCollector.php'; | |||
require_once $__dir . '/HTMLPurifier/ErrorStruct.php'; | |||
require_once $__dir . '/HTMLPurifier/Exception.php'; | |||
require_once $__dir . '/HTMLPurifier/Filter.php'; | |||
require_once $__dir . '/HTMLPurifier/Generator.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLDefinition.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModuleManager.php'; | |||
require_once $__dir . '/HTMLPurifier/IDAccumulator.php'; | |||
require_once $__dir . '/HTMLPurifier/Injector.php'; | |||
require_once $__dir . '/HTMLPurifier/Language.php'; | |||
require_once $__dir . '/HTMLPurifier/LanguageFactory.php'; | |||
require_once $__dir . '/HTMLPurifier/Length.php'; | |||
require_once $__dir . '/HTMLPurifier/Lexer.php'; | |||
require_once $__dir . '/HTMLPurifier/Node.php'; | |||
require_once $__dir . '/HTMLPurifier/PercentEncoder.php'; | |||
require_once $__dir . '/HTMLPurifier/PropertyList.php'; | |||
require_once $__dir . '/HTMLPurifier/PropertyListIterator.php'; | |||
require_once $__dir . '/HTMLPurifier/Queue.php'; | |||
require_once $__dir . '/HTMLPurifier/Strategy.php'; | |||
require_once $__dir . '/HTMLPurifier/StringHash.php'; | |||
require_once $__dir . '/HTMLPurifier/StringHashParser.php'; | |||
require_once $__dir . '/HTMLPurifier/TagTransform.php'; | |||
require_once $__dir . '/HTMLPurifier/Token.php'; | |||
require_once $__dir . '/HTMLPurifier/TokenFactory.php'; | |||
require_once $__dir . '/HTMLPurifier/URI.php'; | |||
require_once $__dir . '/HTMLPurifier/URIDefinition.php'; | |||
require_once $__dir . '/HTMLPurifier/URIFilter.php'; | |||
require_once $__dir . '/HTMLPurifier/URIParser.php'; | |||
require_once $__dir . '/HTMLPurifier/URIScheme.php'; | |||
require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php'; | |||
require_once $__dir . '/HTMLPurifier/UnitConverter.php'; | |||
require_once $__dir . '/HTMLPurifier/VarParser.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/Clone.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/Switch.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/Text.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/URI.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Number.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/AlphaValue.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Background.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Border.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Color.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Composite.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Filter.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Font.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/Length.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ListStyle.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Multiple.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Percentage.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/TextDecoration.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/URI.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Bool.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Nmtokens.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Class.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Color.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/ContentEditable.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/FrameTarget.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/ID.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Pixels.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Length.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/LinkTypes.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/MultiLength.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Email.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Host.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/URI/IPv4.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/URI/IPv6.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/Background.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/BdoDir.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/BgColor.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/BoolToCSS.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/Border.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/EnumToCSS.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/ImgRequired.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/ImgSpace.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/Input.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/Nofollow.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetNoopener.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetNoreferrer.php'; | |||
require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php'; | |||
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php'; | |||
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.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/Optional.php'; | |||
require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php'; | |||
require_once $__dir . '/HTMLPurifier/ChildDef/Table.php'; | |||
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator.php'; | |||
require_once $__dir . '/HTMLPurifier/DefinitionCache/Null.php'; | |||
require_once $__dir . '/HTMLPurifier/DefinitionCache/Serializer.php'; | |||
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php'; | |||
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Memory.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Bdo.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/CommonAttributes.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.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/Legacy.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/List.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/Object.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.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/StyleAttribute.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetNoopener.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetNoreferrer.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Name.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Proprietary.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Strict.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Transitional.php'; | |||
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTML.php'; | |||
require_once $__dir . '/HTMLPurifier/Injector/AutoParagraph.php'; | |||
require_once $__dir . '/HTMLPurifier/Injector/DisplayLinkURI.php'; | |||
require_once $__dir . '/HTMLPurifier/Injector/Linkify.php'; | |||
require_once $__dir . '/HTMLPurifier/Injector/PurifierLinkify.php'; | |||
require_once $__dir . '/HTMLPurifier/Injector/RemoveEmpty.php'; | |||
require_once $__dir . '/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php'; | |||
require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php'; | |||
require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php'; | |||
require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php'; | |||
require_once $__dir . '/HTMLPurifier/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/Core.php'; | |||
require_once $__dir . '/HTMLPurifier/Strategy/FixNesting.php'; | |||
require_once $__dir . '/HTMLPurifier/Strategy/MakeWellFormed.php'; | |||
require_once $__dir . '/HTMLPurifier/Strategy/RemoveForeignElements.php'; | |||
require_once $__dir . '/HTMLPurifier/Strategy/ValidateAttributes.php'; | |||
require_once $__dir . '/HTMLPurifier/TagTransform/Font.php'; | |||
require_once $__dir . '/HTMLPurifier/TagTransform/Simple.php'; | |||
require_once $__dir . '/HTMLPurifier/Token/Comment.php'; | |||
require_once $__dir . '/HTMLPurifier/Token/Tag.php'; | |||
require_once $__dir . '/HTMLPurifier/Token/Empty.php'; | |||
require_once $__dir . '/HTMLPurifier/Token/End.php'; | |||
require_once $__dir . '/HTMLPurifier/Token/Start.php'; | |||
require_once $__dir . '/HTMLPurifier/Token/Text.php'; | |||
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.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/MakeAbsolute.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/file.php'; | |||
require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php'; | |||
require_once $__dir . '/HTMLPurifier/URIScheme/http.php'; | |||
require_once $__dir . '/HTMLPurifier/URIScheme/https.php'; | |||
require_once $__dir . '/HTMLPurifier/URIScheme/mailto.php'; | |||
require_once $__dir . '/HTMLPurifier/URIScheme/news.php'; | |||
require_once $__dir . '/HTMLPurifier/URIScheme/nntp.php'; | |||
require_once $__dir . '/HTMLPurifier/URIScheme/tel.php'; | |||
require_once $__dir . '/HTMLPurifier/VarParser/Flexible.php'; | |||
require_once $__dir . '/HTMLPurifier/VarParser/Native.php'; |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1,148 @@ | |||
<?php | |||
/** | |||
* Defines common attribute collections that modules reference | |||
*/ | |||
class HTMLPurifier_AttrCollections | |||
{ | |||
/** | |||
* Associative array of attribute collections, indexed by name. | |||
* @type array | |||
*/ | |||
public $info = array(); | |||
/** | |||
* Performs all expansions on internal data for use by other inclusions | |||
* It also collects all attribute collection extensions from | |||
* modules | |||
* @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance | |||
* @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members | |||
*/ | |||
public function __construct($attr_types, $modules) | |||
{ | |||
$this->doConstruct($attr_types, $modules); | |||
} | |||
public function doConstruct($attr_types, $modules) | |||
{ | |||
// load extensions from the modules | |||
foreach ($modules as $module) { | |||
foreach ($module->attr_collections as $coll_i => $coll) { | |||
if (!isset($this->info[$coll_i])) { | |||
$this->info[$coll_i] = array(); | |||
} | |||
foreach ($coll as $attr_i => $attr) { | |||
if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) { | |||
// merge in includes | |||
$this->info[$coll_i][$attr_i] = array_merge( | |||
$this->info[$coll_i][$attr_i], | |||
$attr | |||
); | |||
continue; | |||
} | |||
$this->info[$coll_i][$attr_i] = $attr; | |||
} | |||
} | |||
} | |||
// perform internal expansions and inclusions | |||
foreach ($this->info as $name => $attr) { | |||
// merge attribute collections that include others | |||
$this->performInclusions($this->info[$name]); | |||
// replace string identifiers with actual attribute objects | |||
$this->expandIdentifiers($this->info[$name], $attr_types); | |||
} | |||
} | |||
/** | |||
* Takes a reference to an attribute associative array and performs | |||
* all inclusions specified by the zero index. | |||
* @param array &$attr Reference to attribute array | |||
*/ | |||
public function performInclusions(&$attr) | |||
{ | |||
if (!isset($attr[0])) { | |||
return; | |||
} | |||
$merge = $attr[0]; | |||
$seen = array(); // recursion guard | |||
// loop through all the inclusions | |||
for ($i = 0; isset($merge[$i]); $i++) { | |||
if (isset($seen[$merge[$i]])) { | |||
continue; | |||
} | |||
$seen[$merge[$i]] = true; | |||
// foreach attribute of the inclusion, copy it over | |||
if (!isset($this->info[$merge[$i]])) { | |||
continue; | |||
} | |||
foreach ($this->info[$merge[$i]] as $key => $value) { | |||
if (isset($attr[$key])) { | |||
continue; | |||
} // also catches more inclusions | |||
$attr[$key] = $value; | |||
} | |||
if (isset($this->info[$merge[$i]][0])) { | |||
// recursion | |||
$merge = array_merge($merge, $this->info[$merge[$i]][0]); | |||
} | |||
} | |||
unset($attr[0]); | |||
} | |||
/** | |||
* Expands all string identifiers in an attribute array by replacing | |||
* them with the appropriate values inside HTMLPurifier_AttrTypes | |||
* @param array &$attr Reference to attribute array | |||
* @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance | |||
*/ | |||
public function expandIdentifiers(&$attr, $attr_types) | |||
{ | |||
// because foreach will process new elements we add, make sure we | |||
// skip duplicates | |||
$processed = array(); | |||
foreach ($attr as $def_i => $def) { | |||
// skip inclusions | |||
if ($def_i === 0) { | |||
continue; | |||
} | |||
if (isset($processed[$def_i])) { | |||
continue; | |||
} | |||
// determine whether or not attribute is required | |||
if ($required = (strpos($def_i, '*') !== false)) { | |||
// rename the definition | |||
unset($attr[$def_i]); | |||
$def_i = trim($def_i, '*'); | |||
$attr[$def_i] = $def; | |||
} | |||
$processed[$def_i] = true; | |||
// if we've already got a literal object, move on | |||
if (is_object($def)) { | |||
// preserve previous required | |||
$attr[$def_i]->required = ($required || $attr[$def_i]->required); | |||
continue; | |||
} | |||
if ($def === false) { | |||
unset($attr[$def_i]); | |||
continue; | |||
} | |||
if ($t = $attr_types->get($def)) { | |||
$attr[$def_i] = $t; | |||
$attr[$def_i]->required = $required; | |||
} else { | |||
unset($attr[$def_i]); | |||
} | |||
} | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,144 @@ | |||
<?php | |||
/** | |||
* Base class for all validating attribute definitions. | |||
* | |||
* This family of classes forms the core for not only HTML attribute validation, | |||
* but also any sort of string that needs to be validated or cleaned (which | |||
* means CSS properties and composite definitions are defined here too). | |||
* Besides defining (through code) what precisely makes the string valid, | |||
* subclasses are also responsible for cleaning the code if possible. | |||
*/ | |||
abstract class HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Tells us whether or not an HTML attribute is minimized. | |||
* Has no meaning in other contexts. | |||
* @type bool | |||
*/ | |||
public $minimized = false; | |||
/** | |||
* Tells us whether or not an HTML attribute is required. | |||
* Has no meaning in other contexts | |||
* @type bool | |||
*/ | |||
public $required = false; | |||
/** | |||
* Validates and cleans passed string according to a definition. | |||
* | |||
* @param string $string String to be validated and cleaned. | |||
* @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object. | |||
* @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object. | |||
*/ | |||
abstract public function validate($string, $config, $context); | |||
/** | |||
* Convenience method that parses a string as if it were CDATA. | |||
* | |||
* This method process a string in the manner specified at | |||
* <http://www.w3.org/TR/html4/types.html#h-6.2> by removing | |||
* leading and trailing whitespace, ignoring line feeds, and replacing | |||
* carriage returns and tabs with spaces. While most useful for HTML | |||
* attributes specified as CDATA, it can also be applied to most CSS | |||
* values. | |||
* | |||
* @note This method is not entirely standards compliant, as trim() removes | |||
* more types of whitespace than specified in the spec. In practice, | |||
* this is rarely a problem, as those extra characters usually have | |||
* already been removed by HTMLPurifier_Encoder. | |||
* | |||
* @warning This processing is inconsistent with XML's whitespace handling | |||
* as specified by section 3.3.3 and referenced XHTML 1.0 section | |||
* 4.7. However, note that we are NOT necessarily | |||
* parsing XML, thus, this behavior may still be correct. We | |||
* assume that newlines have been normalized. | |||
*/ | |||
public function parseCDATA($string) | |||
{ | |||
$string = trim($string); | |||
$string = str_replace(array("\n", "\t", "\r"), ' ', $string); | |||
return $string; | |||
} | |||
/** | |||
* Factory method for creating this class from a string. | |||
* @param string $string String construction info | |||
* @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string | |||
*/ | |||
public function make($string) | |||
{ | |||
// default implementation, return a flyweight of this object. | |||
// If $string has an effect on the returned object (i.e. you | |||
// need to overload this method), it is best | |||
// to clone or instantiate new copies. (Instantiation is safer.) | |||
return $this; | |||
} | |||
/** | |||
* Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work | |||
* properly. THIS IS A HACK! | |||
* @param string $string a CSS colour definition | |||
* @return string | |||
*/ | |||
protected function mungeRgb($string) | |||
{ | |||
$p = '\s*(\d+(\.\d+)?([%]?))\s*'; | |||
if (preg_match('/(rgba|hsla)\(/', $string)) { | |||
return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string); | |||
} | |||
return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string); | |||
} | |||
/** | |||
* Parses a possibly escaped CSS string and returns the "pure" | |||
* version of it. | |||
*/ | |||
protected function expandCSSEscape($string) | |||
{ | |||
// flexibly parse it | |||
$ret = ''; | |||
for ($i = 0, $c = strlen($string); $i < $c; $i++) { | |||
if ($string[$i] === '\\') { | |||
$i++; | |||
if ($i >= $c) { | |||
$ret .= '\\'; | |||
break; | |||
} | |||
if (ctype_xdigit($string[$i])) { | |||
$code = $string[$i]; | |||
for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { | |||
if (!ctype_xdigit($string[$i])) { | |||
break; | |||
} | |||
$code .= $string[$i]; | |||
} | |||
// We have to be extremely careful when adding | |||
// new characters, to make sure we're not breaking | |||
// the encoding. | |||
$char = HTMLPurifier_Encoder::unichr(hexdec($code)); | |||
if (HTMLPurifier_Encoder::cleanUTF8($char) === '') { | |||
continue; | |||
} | |||
$ret .= $char; | |||
if ($i < $c && trim($string[$i]) !== '') { | |||
$i--; | |||
} | |||
continue; | |||
} | |||
if ($string[$i] === "\n") { | |||
continue; | |||
} | |||
} | |||
$ret .= $string[$i]; | |||
} | |||
return $ret; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,136 @@ | |||
<?php | |||
/** | |||
* Validates the HTML attribute style, otherwise known as CSS. | |||
* @note We don't implement the whole CSS specification, so it might be | |||
* difficult to reuse this component in the context of validating | |||
* actual stylesheet declarations. | |||
* @note If we were really serious about validating the CSS, we would | |||
* tokenize the styles and then parse the tokens. Obviously, we | |||
* are not doing that. Doing that could seriously harm performance, | |||
* but would make these components a lot more viable for a CSS | |||
* filtering solution. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @param string $css | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($css, $config, $context) | |||
{ | |||
$css = $this->parseCDATA($css); | |||
$definition = $config->getCSSDefinition(); | |||
$allow_duplicates = $config->get("CSS.AllowDuplicates"); | |||
// According to the CSS2.1 spec, the places where a | |||
// non-delimiting semicolon can appear are in strings | |||
// escape sequences. So here is some dumb hack to | |||
// handle quotes. | |||
$len = strlen($css); | |||
$accum = ""; | |||
$declarations = array(); | |||
$quoted = false; | |||
for ($i = 0; $i < $len; $i++) { | |||
$c = strcspn($css, ";'\"", $i); | |||
$accum .= substr($css, $i, $c); | |||
$i += $c; | |||
if ($i == $len) break; | |||
$d = $css[$i]; | |||
if ($quoted) { | |||
$accum .= $d; | |||
if ($d == $quoted) { | |||
$quoted = false; | |||
} | |||
} else { | |||
if ($d == ";") { | |||
$declarations[] = $accum; | |||
$accum = ""; | |||
} else { | |||
$accum .= $d; | |||
$quoted = $d; | |||
} | |||
} | |||
} | |||
if ($accum != "") $declarations[] = $accum; | |||
$propvalues = array(); | |||
$new_declarations = ''; | |||
/** | |||
* Name of the current CSS property being validated. | |||
*/ | |||
$property = false; | |||
$context->register('CurrentCSSProperty', $property); | |||
foreach ($declarations as $declaration) { | |||
if (!$declaration) { | |||
continue; | |||
} | |||
if (!strpos($declaration, ':')) { | |||
continue; | |||
} | |||
list($property, $value) = explode(':', $declaration, 2); | |||
$property = trim($property); | |||
$value = trim($value); | |||
$ok = false; | |||
do { | |||
if (isset($definition->info[$property])) { | |||
$ok = true; | |||
break; | |||
} | |||
if (ctype_lower($property)) { | |||
break; | |||
} | |||
$property = strtolower($property); | |||
if (isset($definition->info[$property])) { | |||
$ok = true; | |||
break; | |||
} | |||
} while (0); | |||
if (!$ok) { | |||
continue; | |||
} | |||
// inefficient call, since the validator will do this again | |||
if (strtolower(trim($value)) !== 'inherit') { | |||
// inherit works for everything (but only on the base property) | |||
$result = $definition->info[$property]->validate( | |||
$value, | |||
$config, | |||
$context | |||
); | |||
} else { | |||
$result = 'inherit'; | |||
} | |||
if ($result === false) { | |||
continue; | |||
} | |||
if ($allow_duplicates) { | |||
$new_declarations .= "$property:$result;"; | |||
} else { | |||
$propvalues[$property] = $result; | |||
} | |||
} | |||
$context->destroy('CurrentCSSProperty'); | |||
// procedure does not write the new CSS simultaneously, so it's | |||
// slightly inefficient, but it's the only way of getting rid of | |||
// duplicates. Perhaps config to optimize it, but not now. | |||
foreach ($propvalues as $prop => $value) { | |||
$new_declarations .= "$prop:$value;"; | |||
} | |||
return $new_declarations ? $new_declarations : false; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,34 @@ | |||
<?php | |||
class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number | |||
{ | |||
public function __construct() | |||
{ | |||
parent::__construct(false); // opacity is non-negative, but we will clamp it | |||
} | |||
/** | |||
* @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); | |||
if ($result === false) { | |||
return $result; | |||
} | |||
$float = (float)$result; | |||
if ($float < 0.0) { | |||
$result = '0'; | |||
} | |||
if ($float > 1.0) { | |||
$result = '1'; | |||
} | |||
return $result; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,113 @@ | |||
<?php | |||
/** | |||
* Validates shorthand CSS property background. | |||
* @warning Does not support url tokens that have internal spaces. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Local copy of component validators. | |||
* @type HTMLPurifier_AttrDef[] | |||
* @note See HTMLPurifier_AttrDef_Font::$info for a similar impl. | |||
*/ | |||
protected $info; | |||
/** | |||
* @param HTMLPurifier_Config $config | |||
*/ | |||
public function __construct($config) | |||
{ | |||
$def = $config->getCSSDefinition(); | |||
$this->info['background-color'] = $def->info['background-color']; | |||
$this->info['background-image'] = $def->info['background-image']; | |||
$this->info['background-repeat'] = $def->info['background-repeat']; | |||
$this->info['background-attachment'] = $def->info['background-attachment']; | |||
$this->info['background-position'] = $def->info['background-position']; | |||
$this->info['background-size'] = $def->info['background-size']; | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
// regular pre-processing | |||
$string = $this->parseCDATA($string); | |||
if ($string === '') { | |||
return false; | |||
} | |||
// munge rgb() decl if necessary | |||
$string = $this->mungeRgb($string); | |||
// assumes URI doesn't have spaces in it | |||
$bits = explode(' ', $string); // bits to process | |||
$caught = array(); | |||
$caught['color'] = false; | |||
$caught['image'] = false; | |||
$caught['repeat'] = false; | |||
$caught['attachment'] = false; | |||
$caught['position'] = false; | |||
$caught['size'] = false; | |||
$i = 0; // number of catches | |||
foreach ($bits as $bit) { | |||
if ($bit === '') { | |||
continue; | |||
} | |||
foreach ($caught as $key => $status) { | |||
if ($key != 'position') { | |||
if ($status !== false) { | |||
continue; | |||
} | |||
$r = $this->info['background-' . $key]->validate($bit, $config, $context); | |||
} else { | |||
$r = $bit; | |||
} | |||
if ($r === false) { | |||
continue; | |||
} | |||
if ($key == 'position') { | |||
if ($caught[$key] === false) { | |||
$caught[$key] = ''; | |||
} | |||
$caught[$key] .= $r . ' '; | |||
} else { | |||
$caught[$key] = $r; | |||
} | |||
$i++; | |||
break; | |||
} | |||
} | |||
if (!$i) { | |||
return false; | |||
} | |||
if ($caught['position'] !== false) { | |||
$caught['position'] = $this->info['background-position']-> | |||
validate($caught['position'], $config, $context); | |||
} | |||
$ret = array(); | |||
foreach ($caught as $value) { | |||
if ($value === false) { | |||
continue; | |||
} | |||
$ret[] = $value; | |||
} | |||
if (empty($ret)) { | |||
return false; | |||
} | |||
return implode(' ', $ret); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,157 @@ | |||
<?php | |||
/* W3C says: | |||
[ // adjective and number must be in correct order, even if | |||
// you could switch them without introducing ambiguity. | |||
// some browsers support that syntax | |||
[ | |||
<percentage> | <length> | left | center | right | |||
] | |||
[ | |||
<percentage> | <length> | top | center | bottom | |||
]? | |||
] | | |||
[ // this signifies that the vertical and horizontal adjectives | |||
// can be arbitrarily ordered, however, there can only be two, | |||
// one of each, or none at all | |||
[ | |||
left | center | right | |||
] || | |||
[ | |||
top | center | bottom | |||
] | |||
] | |||
top, left = 0% | |||
center, (none) = 50% | |||
bottom, right = 100% | |||
*/ | |||
/* QuirksMode says: | |||
keyword + length/percentage must be ordered correctly, as per W3C | |||
Internet Explorer and Opera, however, support arbitrary ordering. We | |||
should fix it up. | |||
Minor issue though, not strictly necessary. | |||
*/ | |||
// control freaks may appreciate the ability to convert these to | |||
// percentages or something, but it's not necessary | |||
/** | |||
* Validates the value of background-position. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type HTMLPurifier_AttrDef_CSS_Length | |||
*/ | |||
protected $length; | |||
/** | |||
* @type HTMLPurifier_AttrDef_CSS_Percentage | |||
*/ | |||
protected $percentage; | |||
public function __construct() | |||
{ | |||
$this->length = new HTMLPurifier_AttrDef_CSS_Length(); | |||
$this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage(); | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$string = $this->parseCDATA($string); | |||
$bits = explode(' ', $string); | |||
$keywords = array(); | |||
$keywords['h'] = false; // left, right | |||
$keywords['v'] = false; // top, bottom | |||
$keywords['ch'] = false; // center (first word) | |||
$keywords['cv'] = false; // center (second word) | |||
$measures = array(); | |||
$i = 0; | |||
$lookup = array( | |||
'top' => 'v', | |||
'bottom' => 'v', | |||
'left' => 'h', | |||
'right' => 'h', | |||
'center' => 'c' | |||
); | |||
foreach ($bits as $bit) { | |||
if ($bit === '') { | |||
continue; | |||
} | |||
// test for keyword | |||
$lbit = ctype_lower($bit) ? $bit : strtolower($bit); | |||
if (isset($lookup[$lbit])) { | |||
$status = $lookup[$lbit]; | |||
if ($status == 'c') { | |||
if ($i == 0) { | |||
$status = 'ch'; | |||
} else { | |||
$status = 'cv'; | |||
} | |||
} | |||
$keywords[$status] = $lbit; | |||
$i++; | |||
} | |||
// test for length | |||
$r = $this->length->validate($bit, $config, $context); | |||
if ($r !== false) { | |||
$measures[] = $r; | |||
$i++; | |||
} | |||
// test for percentage | |||
$r = $this->percentage->validate($bit, $config, $context); | |||
if ($r !== false) { | |||
$measures[] = $r; | |||
$i++; | |||
} | |||
} | |||
if (!$i) { | |||
return false; | |||
} // no valid values were caught | |||
$ret = array(); | |||
// first keyword | |||
if ($keywords['h']) { | |||
$ret[] = $keywords['h']; | |||
} elseif ($keywords['ch']) { | |||
$ret[] = $keywords['ch']; | |||
$keywords['cv'] = false; // prevent re-use: center = center center | |||
} elseif (count($measures)) { | |||
$ret[] = array_shift($measures); | |||
} | |||
if ($keywords['v']) { | |||
$ret[] = $keywords['v']; | |||
} elseif ($keywords['cv']) { | |||
$ret[] = $keywords['cv']; | |||
} elseif (count($measures)) { | |||
$ret[] = array_shift($measures); | |||
} | |||
if (empty($ret)) { | |||
return false; | |||
} | |||
return implode(' ', $ret); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,56 @@ | |||
<?php | |||
/** | |||
* Validates the border property as defined by CSS. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Local copy of properties this property is shorthand for. | |||
* @type HTMLPurifier_AttrDef[] | |||
*/ | |||
protected $info = array(); | |||
/** | |||
* @param HTMLPurifier_Config $config | |||
*/ | |||
public function __construct($config) | |||
{ | |||
$def = $config->getCSSDefinition(); | |||
$this->info['border-width'] = $def->info['border-width']; | |||
$this->info['border-style'] = $def->info['border-style']; | |||
$this->info['border-top-color'] = $def->info['border-top-color']; | |||
} | |||
/** | |||
* @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->mungeRgb($string); | |||
$bits = explode(' ', $string); | |||
$done = array(); // segments we've finished | |||
$ret = ''; // return value | |||
foreach ($bits as $bit) { | |||
foreach ($this->info as $propname => $validator) { | |||
if (isset($done[$propname])) { | |||
continue; | |||
} | |||
$r = $validator->validate($bit, $config, $context); | |||
if ($r !== false) { | |||
$ret .= $r . ' '; | |||
$done[$propname] = true; | |||
break; | |||
} | |||
} | |||
} | |||
return rtrim($ret); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,161 @@ | |||
<?php | |||
/** | |||
* Validates Color as defined by CSS. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type HTMLPurifier_AttrDef_CSS_AlphaValue | |||
*/ | |||
protected $alpha; | |||
public function __construct() | |||
{ | |||
$this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue(); | |||
} | |||
/** | |||
* @param string $color | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($color, $config, $context) | |||
{ | |||
static $colors = null; | |||
if ($colors === null) { | |||
$colors = $config->get('Core.ColorKeywords'); | |||
} | |||
$color = trim($color); | |||
if ($color === '') { | |||
return false; | |||
} | |||
$lower = strtolower($color); | |||
if (isset($colors[$lower])) { | |||
return $colors[$lower]; | |||
} | |||
if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) { | |||
$length = strlen($color); | |||
if (strpos($color, ')') !== $length - 1) { | |||
return false; | |||
} | |||
// get used function : rgb, rgba, hsl or hsla | |||
$function = $matches[1]; | |||
$parameters_size = 3; | |||
$alpha_channel = false; | |||
if (substr($function, -1) === 'a') { | |||
$parameters_size = 4; | |||
$alpha_channel = true; | |||
} | |||
/* | |||
* Allowed types for values : | |||
* parameter_position => [type => max_value] | |||
*/ | |||
$allowed_types = array( | |||
1 => array('percentage' => 100, 'integer' => 255), | |||
2 => array('percentage' => 100, 'integer' => 255), | |||
3 => array('percentage' => 100, 'integer' => 255), | |||
); | |||
$allow_different_types = false; | |||
if (strpos($function, 'hsl') !== false) { | |||
$allowed_types = array( | |||
1 => array('integer' => 360), | |||
2 => array('percentage' => 100), | |||
3 => array('percentage' => 100), | |||
); | |||
$allow_different_types = true; | |||
} | |||
$values = trim(str_replace($function, '', $color), ' ()'); | |||
$parts = explode(',', $values); | |||
if (count($parts) !== $parameters_size) { | |||
return false; | |||
} | |||
$type = false; | |||
$new_parts = array(); | |||
$i = 0; | |||
foreach ($parts as $part) { | |||
$i++; | |||
$part = trim($part); | |||
if ($part === '') { | |||
return false; | |||
} | |||
// different check for alpha channel | |||
if ($alpha_channel === true && $i === count($parts)) { | |||
$result = $this->alpha->validate($part, $config, $context); | |||
if ($result === false) { | |||
return false; | |||
} | |||
$new_parts[] = (string)$result; | |||
continue; | |||
} | |||
if (substr($part, -1) === '%') { | |||
$current_type = 'percentage'; | |||
} else { | |||
$current_type = 'integer'; | |||
} | |||
if (!array_key_exists($current_type, $allowed_types[$i])) { | |||
return false; | |||
} | |||
if (!$type) { | |||
$type = $current_type; | |||
} | |||
if ($allow_different_types === false && $type != $current_type) { | |||
return false; | |||
} | |||
$max_value = $allowed_types[$i][$current_type]; | |||
if ($current_type == 'integer') { | |||
// Return value between range 0 -> $max_value | |||
$new_parts[] = (int)max(min($part, $max_value), 0); | |||
} elseif ($current_type == 'percentage') { | |||
$new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%'; | |||
} | |||
} | |||
$new_values = implode(',', $new_parts); | |||
$color = $function . '(' . $new_values . ')'; | |||
} else { | |||
// hexadecimal handling | |||
if ($color[0] === '#') { | |||
$hex = substr($color, 1); | |||
} else { | |||
$hex = $color; | |||
$color = '#' . $color; | |||
} | |||
$length = strlen($hex); | |||
if ($length !== 3 && $length !== 6) { | |||
return false; | |||
} | |||
if (!ctype_xdigit($hex)) { | |||
return false; | |||
} | |||
} | |||
return $color; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,48 @@ | |||
<?php | |||
/** | |||
* Allows multiple validators to attempt to validate attribute. | |||
* | |||
* Composite is just what it sounds like: a composite of many validators. | |||
* This means that multiple HTMLPurifier_AttrDef objects will have a whack | |||
* at the string. If one of them passes, that's what is returned. This is | |||
* especially useful for CSS values, which often are a choice between | |||
* an enumerated set of predefined values or a flexible data type. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* List of objects that may process strings. | |||
* @type HTMLPurifier_AttrDef[] | |||
* @todo Make protected | |||
*/ | |||
public $defs; | |||
/** | |||
* @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects | |||
*/ | |||
public function __construct($defs) | |||
{ | |||
$this->defs = $defs; | |||
} | |||
/** | |||
* @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) { | |||
$result = $this->defs[$i]->validate($string, $config, $context); | |||
if ($result !== false) { | |||
return $result; | |||
} | |||
} | |||
return false; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,44 @@ | |||
<?php | |||
/** | |||
* Decorator which enables CSS properties to be disabled for specific elements. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type HTMLPurifier_AttrDef | |||
*/ | |||
public $def; | |||
/** | |||
* @type string | |||
*/ | |||
public $element; | |||
/** | |||
* @param HTMLPurifier_AttrDef $def Definition to wrap | |||
* @param string $element Element to deny | |||
*/ | |||
public function __construct($def, $element) | |||
{ | |||
$this->def = $def; | |||
$this->element = $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) | |||
{ | |||
$token = $context->get('CurrentToken', true); | |||
if ($token && $token->name == $this->element) { | |||
return false; | |||
} | |||
return $this->def->validate($string, $config, $context); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,77 @@ | |||
<?php | |||
/** | |||
* Microsoft's proprietary filter: CSS property | |||
* @note Currently supports the alpha filter. In the future, this will | |||
* probably need an extensible framework | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type HTMLPurifier_AttrDef_Integer | |||
*/ | |||
protected $intValidator; | |||
public function __construct() | |||
{ | |||
$this->intValidator = new HTMLPurifier_AttrDef_Integer(); | |||
} | |||
/** | |||
* @param string $value | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($value, $config, $context) | |||
{ | |||
$value = $this->parseCDATA($value); | |||
if ($value === 'none') { | |||
return $value; | |||
} | |||
// if we looped this we could support multiple filters | |||
$function_length = strcspn($value, '('); | |||
$function = trim(substr($value, 0, $function_length)); | |||
if ($function !== 'alpha' && | |||
$function !== 'Alpha' && | |||
$function !== 'progid:DXImageTransform.Microsoft.Alpha' | |||
) { | |||
return false; | |||
} | |||
$cursor = $function_length + 1; | |||
$parameters_length = strcspn($value, ')', $cursor); | |||
$parameters = substr($value, $cursor, $parameters_length); | |||
$params = explode(',', $parameters); | |||
$ret_params = array(); | |||
$lookup = array(); | |||
foreach ($params as $param) { | |||
list($key, $value) = explode('=', $param); | |||
$key = trim($key); | |||
$value = trim($value); | |||
if (isset($lookup[$key])) { | |||
continue; | |||
} | |||
if ($key !== 'opacity') { | |||
continue; | |||
} | |||
$value = $this->intValidator->validate($value, $config, $context); | |||
if ($value === false) { | |||
continue; | |||
} | |||
$int = (int)$value; | |||
if ($int > 100) { | |||
$value = '100'; | |||
} | |||
if ($int < 0) { | |||
$value = '0'; | |||
} | |||
$ret_params[] = "$key=$value"; | |||
$lookup[$key] = true; | |||
} | |||
$ret_parameters = implode(',', $ret_params); | |||
$ret_function = "$function($ret_parameters)"; | |||
return $ret_function; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,176 @@ | |||
<?php | |||
/** | |||
* Validates shorthand CSS property font. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Local copy of validators | |||
* @type HTMLPurifier_AttrDef[] | |||
* @note If we moved specific CSS property definitions to their own | |||
* classes instead of having them be assembled at run time by | |||
* CSSDefinition, this wouldn't be necessary. We'd instantiate | |||
* our own copies. | |||
*/ | |||
protected $info = array(); | |||
/** | |||
* @param HTMLPurifier_Config $config | |||
*/ | |||
public function __construct($config) | |||
{ | |||
$def = $config->getCSSDefinition(); | |||
$this->info['font-style'] = $def->info['font-style']; | |||
$this->info['font-variant'] = $def->info['font-variant']; | |||
$this->info['font-weight'] = $def->info['font-weight']; | |||
$this->info['font-size'] = $def->info['font-size']; | |||
$this->info['line-height'] = $def->info['line-height']; | |||
$this->info['font-family'] = $def->info['font-family']; | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
static $system_fonts = array( | |||
'caption' => true, | |||
'icon' => true, | |||
'menu' => true, | |||
'message-box' => true, | |||
'small-caption' => true, | |||
'status-bar' => true | |||
); | |||
// regular pre-processing | |||
$string = $this->parseCDATA($string); | |||
if ($string === '') { | |||
return false; | |||
} | |||
// check if it's one of the keywords | |||
$lowercase_string = strtolower($string); | |||
if (isset($system_fonts[$lowercase_string])) { | |||
return $lowercase_string; | |||
} | |||
$bits = explode(' ', $string); // bits to process | |||
$stage = 0; // this indicates what we're looking for | |||
$caught = array(); // which stage 0 properties have we caught? | |||
$stage_1 = array('font-style', 'font-variant', 'font-weight'); | |||
$final = ''; // output | |||
for ($i = 0, $size = count($bits); $i < $size; $i++) { | |||
if ($bits[$i] === '') { | |||
continue; | |||
} | |||
switch ($stage) { | |||
case 0: // attempting to catch font-style, font-variant or font-weight | |||
foreach ($stage_1 as $validator_name) { | |||
if (isset($caught[$validator_name])) { | |||
continue; | |||
} | |||
$r = $this->info[$validator_name]->validate( | |||
$bits[$i], | |||
$config, | |||
$context | |||
); | |||
if ($r !== false) { | |||
$final .= $r . ' '; | |||
$caught[$validator_name] = true; | |||
break; | |||
} | |||
} | |||
// all three caught, continue on | |||
if (count($caught) >= 3) { | |||
$stage = 1; | |||
} | |||
if ($r !== false) { | |||
break; | |||
} | |||
case 1: // attempting to catch font-size and perhaps line-height | |||
$found_slash = false; | |||
if (strpos($bits[$i], '/') !== false) { | |||
list($font_size, $line_height) = | |||
explode('/', $bits[$i]); | |||
if ($line_height === '') { | |||
// ooh, there's a space after the slash! | |||
$line_height = false; | |||
$found_slash = true; | |||
} | |||
} else { | |||
$font_size = $bits[$i]; | |||
$line_height = false; | |||
} | |||
$r = $this->info['font-size']->validate( | |||
$font_size, | |||
$config, | |||
$context | |||
); | |||
if ($r !== false) { | |||
$final .= $r; | |||
// attempt to catch line-height | |||
if ($line_height === false) { | |||
// we need to scroll forward | |||
for ($j = $i + 1; $j < $size; $j++) { | |||
if ($bits[$j] === '') { | |||
continue; | |||
} | |||
if ($bits[$j] === '/') { | |||
if ($found_slash) { | |||
return false; | |||
} else { | |||
$found_slash = true; | |||
continue; | |||
} | |||
} | |||
$line_height = $bits[$j]; | |||
break; | |||
} | |||
} else { | |||
// slash already found | |||
$found_slash = true; | |||
$j = $i; | |||
} | |||
if ($found_slash) { | |||
$i = $j; | |||
$r = $this->info['line-height']->validate( | |||
$line_height, | |||
$config, | |||
$context | |||
); | |||
if ($r !== false) { | |||
$final .= '/' . $r; | |||
} | |||
} | |||
$final .= ' '; | |||
$stage = 2; | |||
break; | |||
} | |||
return false; | |||
case 2: // attempting to catch font-family | |||
$font_family = | |||
implode(' ', array_slice($bits, $i, $size - $i)); | |||
$r = $this->info['font-family']->validate( | |||
$font_family, | |||
$config, | |||
$context | |||
); | |||
if ($r !== false) { | |||
$final .= $r . ' '; | |||
// processing completed successfully | |||
return rtrim($final); | |||
} | |||
return false; | |||
} | |||
} | |||
return false; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,219 @@ | |||
<?php | |||
/** | |||
* Validates a font family list according to CSS spec | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef | |||
{ | |||
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( | |||
'serif' => true, | |||
'sans-serif' => true, | |||
'monospace' => true, | |||
'fantasy' => true, | |||
'cursive' => true | |||
); | |||
$allowed_fonts = $config->get('CSS.AllowedFonts'); | |||
// assume that no font names contain commas in them | |||
$fonts = explode(',', $string); | |||
$final = ''; | |||
foreach ($fonts as $font) { | |||
$font = trim($font); | |||
if ($font === '') { | |||
continue; | |||
} | |||
// match a generic name | |||
if (isset($generic_names[$font])) { | |||
if ($allowed_fonts === null || isset($allowed_fonts[$font])) { | |||
$final .= $font . ', '; | |||
} | |||
continue; | |||
} | |||
// match a quoted name | |||
if ($font[0] === '"' || $font[0] === "'") { | |||
$length = strlen($font); | |||
if ($length <= 2) { | |||
continue; | |||
} | |||
$quote = $font[0]; | |||
if ($font[$length - 1] !== $quote) { | |||
continue; | |||
} | |||
$font = substr($font, 1, $length - 2); | |||
} | |||
$font = $this->expandCSSEscape($font); | |||
// $font is a pure representation of the font name | |||
if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) { | |||
continue; | |||
} | |||
if (ctype_alnum($font) && $font !== '') { | |||
// very simple font, allow it in unharmed | |||
$final .= $font . ', '; | |||
continue; | |||
} | |||
// bugger out on whitespace. form feed (0C) really | |||
// shouldn't show up regardless | |||
$font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font); | |||
// Here, there are various classes of characters which need | |||
// to be treated differently: | |||
// - Alphanumeric characters are essentially safe. We | |||
// handled these above. | |||
// - Spaces require quoting, though most parsers will do | |||
// the right thing if there aren't any characters that | |||
// can be misinterpreted | |||
// - 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: "MS 明朝" 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+MS+明朝|ゴシック> | |||
// 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. | |||
// Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of | |||
// 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, ', '); | |||
if ($final === '') { | |||
return false; | |||
} | |||
return $final; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -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 |
@@ -0,0 +1,56 @@ | |||
<?php | |||
/** | |||
* Decorator which enables !important to be used in CSS values. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type HTMLPurifier_AttrDef | |||
*/ | |||
public $def; | |||
/** | |||
* @type bool | |||
*/ | |||
public $allow; | |||
/** | |||
* @param HTMLPurifier_AttrDef $def Definition to wrap | |||
* @param bool $allow Whether or not to allow !important | |||
*/ | |||
public function __construct($def, $allow = false) | |||
{ | |||
$this->def = $def; | |||
$this->allow = $allow; | |||
} | |||
/** | |||
* 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) | |||
{ | |||
// test for ! and important tokens | |||
$string = trim($string); | |||
$is_important = false; | |||
// :TODO: optimization: test directly for !important and ! important | |||
if (strlen($string) >= 9 && substr($string, -9) === 'important') { | |||
$temp = rtrim(substr($string, 0, -9)); | |||
// use a temp, because we might want to restore important | |||
if (strlen($temp) >= 1 && substr($temp, -1) === '!') { | |||
$string = rtrim(substr($temp, 0, -1)); | |||
$is_important = true; | |||
} | |||
} | |||
$string = $this->def->validate($string, $config, $context); | |||
if ($this->allow && $is_important) { | |||
$string .= ' !important'; | |||
} | |||
return $string; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,77 @@ | |||
<?php | |||
/** | |||
* Represents a Length as defined by CSS. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type HTMLPurifier_Length|string | |||
*/ | |||
protected $min; | |||
/** | |||
* @type HTMLPurifier_Length|string | |||
*/ | |||
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->max = $max !== null ? HTMLPurifier_Length::make($max) : null; | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$string = $this->parseCDATA($string); | |||
// Optimizations | |||
if ($string === '') { | |||
return false; | |||
} | |||
if ($string === '0') { | |||
return '0'; | |||
} | |||
if (strlen($string) === 1) { | |||
return false; | |||
} | |||
$length = HTMLPurifier_Length::make($string); | |||
if (!$length->isValid()) { | |||
return false; | |||
} | |||
if ($this->min) { | |||
$c = $length->compareTo($this->min); | |||
if ($c === false) { | |||
return false; | |||
} | |||
if ($c < 0) { | |||
return false; | |||
} | |||
} | |||
if ($this->max) { | |||
$c = $length->compareTo($this->max); | |||
if ($c === false) { | |||
return false; | |||
} | |||
if ($c > 0) { | |||
return false; | |||
} | |||
} | |||
return $length->toString(); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,112 @@ | |||
<?php | |||
/** | |||
* Validates shorthand CSS property list-style. | |||
* @warning Does not support url tokens that have internal spaces. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Local copy of validators. | |||
* @type HTMLPurifier_AttrDef[] | |||
* @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl. | |||
*/ | |||
protected $info; | |||
/** | |||
* @param HTMLPurifier_Config $config | |||
*/ | |||
public function __construct($config) | |||
{ | |||
$def = $config->getCSSDefinition(); | |||
$this->info['list-style-type'] = $def->info['list-style-type']; | |||
$this->info['list-style-position'] = $def->info['list-style-position']; | |||
$this->info['list-style-image'] = $def->info['list-style-image']; | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
// regular pre-processing | |||
$string = $this->parseCDATA($string); | |||
if ($string === '') { | |||
return false; | |||
} | |||
// assumes URI doesn't have spaces in it | |||
$bits = explode(' ', strtolower($string)); // bits to process | |||
$caught = array(); | |||
$caught['type'] = false; | |||
$caught['position'] = false; | |||
$caught['image'] = false; | |||
$i = 0; // number of catches | |||
$none = false; | |||
foreach ($bits as $bit) { | |||
if ($i >= 3) { | |||
return; | |||
} // optimization bit | |||
if ($bit === '') { | |||
continue; | |||
} | |||
foreach ($caught as $key => $status) { | |||
if ($status !== false) { | |||
continue; | |||
} | |||
$r = $this->info['list-style-' . $key]->validate($bit, $config, $context); | |||
if ($r === false) { | |||
continue; | |||
} | |||
if ($r === 'none') { | |||
if ($none) { | |||
continue; | |||
} else { | |||
$none = true; | |||
} | |||
if ($key == 'image') { | |||
continue; | |||
} | |||
} | |||
$caught[$key] = $r; | |||
$i++; | |||
break; | |||
} | |||
} | |||
if (!$i) { | |||
return false; | |||
} | |||
$ret = array(); | |||
// construct 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); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,71 @@ | |||
<?php | |||
/** | |||
* Framework class for strings that involve multiple values. | |||
* | |||
* Certain CSS properties such as border-width and margin allow multiple | |||
* lengths to be specified. This class can take a vanilla border-width | |||
* definition and multiply it, usually into a max of four. | |||
* | |||
* @note Even though the CSS specification isn't clear about it, inherit | |||
* can only be used alone: it will never manifest as part of a multi | |||
* shorthand declaration. Thus, this class does not allow inherit. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Instance of component definition to defer validation to. | |||
* @type HTMLPurifier_AttrDef | |||
* @todo Make protected | |||
*/ | |||
public $single; | |||
/** | |||
* Max number of values allowed. | |||
* @todo Make protected | |||
*/ | |||
public $max; | |||
/** | |||
* @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply | |||
* @param int $max Max number of values allowed (usually four) | |||
*/ | |||
public function __construct($single, $max = 4) | |||
{ | |||
$this->single = $single; | |||
$this->max = $max; | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$string = $this->mungeRgb($this->parseCDATA($string)); | |||
if ($string === '') { | |||
return false; | |||
} | |||
$parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n | |||
$length = count($parts); | |||
$final = ''; | |||
for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) { | |||
if (ctype_space($parts[$i])) { | |||
continue; | |||
} | |||
$result = $this->single->validate($parts[$i], $config, $context); | |||
if ($result !== false) { | |||
$final .= $result . ' '; | |||
$num++; | |||
} | |||
} | |||
if ($final === '') { | |||
return false; | |||
} | |||
return rtrim($final); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,90 @@ | |||
<?php | |||
/** | |||
* Validates a number as defined by the CSS spec. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Indicates whether or not only positive values are allowed. | |||
* @type bool | |||
*/ | |||
protected $non_negative = false; | |||
/** | |||
* @param bool $non_negative indicates whether negatives are forbidden | |||
*/ | |||
public function __construct($non_negative = false) | |||
{ | |||
$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 | |||
* variables should not be used without checking HTMLPurifier_Length | |||
*/ | |||
public function validate($number, $config, $context) | |||
{ | |||
$number = $this->parseCDATA($number); | |||
if ($number === '') { | |||
return false; | |||
} | |||
if ($number === '0') { | |||
return '0'; | |||
} | |||
$sign = ''; | |||
switch ($number[0]) { | |||
case '-': | |||
if ($this->non_negative) { | |||
return false; | |||
} | |||
$sign = '-'; | |||
case '+': | |||
$number = substr($number, 1); | |||
} | |||
if (ctype_digit($number)) { | |||
$number = ltrim($number, '0'); | |||
return $number ? $sign . $number : '0'; | |||
} | |||
// Period is the only non-numeric character allowed | |||
if (strpos($number, '.') === false) { | |||
return false; | |||
} | |||
list($left, $right) = explode('.', $number, 2); | |||
if ($left === '' && $right === '') { | |||
return false; | |||
} | |||
if ($left !== '' && !ctype_digit($left)) { | |||
return false; | |||
} | |||
// Remove leading zeros until positive number or a zero stays left | |||
if (ltrim($left, '0') != '') { | |||
$left = ltrim($left, '0'); | |||
} else { | |||
$left = '0'; | |||
} | |||
$right = rtrim($right, '0'); | |||
if ($right === '') { | |||
return $left ? $sign . $left : '0'; | |||
} elseif (!ctype_digit($right)) { | |||
return false; | |||
} | |||
return $sign . $left . '.' . $right; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,54 @@ | |||
<?php | |||
/** | |||
* Validates a Percentage as defined by the CSS spec. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Instance to defer number validation to. | |||
* @type HTMLPurifier_AttrDef_CSS_Number | |||
*/ | |||
protected $number_def; | |||
/** | |||
* @param bool $non_negative Whether to forbid negative values | |||
*/ | |||
public function __construct($non_negative = false) | |||
{ | |||
$this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative); | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$string = $this->parseCDATA($string); | |||
if ($string === '') { | |||
return false; | |||
} | |||
$length = strlen($string); | |||
if ($length === 1) { | |||
return false; | |||
} | |||
if ($string[$length - 1] !== '%') { | |||
return false; | |||
} | |||
$number = substr($string, 0, $length - 1); | |||
$number = $this->number_def->validate($number, $config, $context); | |||
if ($number === false) { | |||
return false; | |||
} | |||
return "$number%"; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,46 @@ | |||
<?php | |||
/** | |||
* Validates the value for the CSS property text-decoration | |||
* @note This class could be generalized into a version that acts sort of | |||
* like Enum except you can compound the allowed values. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
static $allowed_values = array( | |||
'line-through' => true, | |||
'overline' => true, | |||
'underline' => true, | |||
); | |||
$string = strtolower($this->parseCDATA($string)); | |||
if ($string === 'none') { | |||
return $string; | |||
} | |||
$parts = explode(' ', $string); | |||
$final = ''; | |||
foreach ($parts as $part) { | |||
if (isset($allowed_values[$part])) { | |||
$final .= $part . ' '; | |||
} | |||
} | |||
$final = rtrim($final); | |||
if ($final === '') { | |||
return false; | |||
} | |||
return $final; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,77 @@ | |||
<?php | |||
/** | |||
* Validates a URI in CSS syntax, which uses url('http://example.com') | |||
* @note While theoretically speaking a URI in a CSS document could | |||
* be non-embedded, as of CSS2 there is no such usage so we're | |||
* generalizing it. This may need to be changed in the future. | |||
* @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as | |||
* the separator, you cannot put a literal semicolon in | |||
* in the URI. Try percent encoding it, in that case. | |||
*/ | |||
class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI | |||
{ | |||
public function __construct() | |||
{ | |||
parent::__construct(true); // always embedded | |||
} | |||
/** | |||
* @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 | |||
// the parent object | |||
$uri_string = $this->parseCDATA($uri_string); | |||
if (strpos($uri_string, 'url(') !== 0) { | |||
return false; | |||
} | |||
$uri_string = substr($uri_string, 4); | |||
if (strlen($uri_string) == 0) { | |||
return false; | |||
} | |||
$new_length = strlen($uri_string) - 1; | |||
if ($uri_string[$new_length] != ')') { | |||
return false; | |||
} | |||
$uri = trim(substr($uri_string, 0, $new_length)); | |||
if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) { | |||
$quote = $uri[0]; | |||
$new_length = strlen($uri) - 1; | |||
if ($uri[$new_length] !== $quote) { | |||
return false; | |||
} | |||
$uri = substr($uri, 1, $new_length - 1); | |||
} | |||
$uri = $this->expandCSSEscape($uri); | |||
$result = parent::validate($uri, $config, $context); | |||
if ($result === false) { | |||
return false; | |||
} | |||
// extra sanity check; should have been done by URI | |||
$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\")"; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -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 |
@@ -0,0 +1,73 @@ | |||
<?php | |||
// Enum = Enumerated | |||
/** | |||
* Validates a keyword against a list of valid values. | |||
* @warning The case-insensitive compare of this function uses PHP's | |||
* built-in strtolower and ctype_lower functions, which may | |||
* cause problems with international comparisons | |||
*/ | |||
class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Lookup table of valid values. | |||
* @type array | |||
* @todo Make protected | |||
*/ | |||
public $valid_values = array(); | |||
/** | |||
* Bool indicating whether or not enumeration is case sensitive. | |||
* @note In general this is always case insensitive. | |||
*/ | |||
protected $case_sensitive = false; // values according to W3C spec | |||
/** | |||
* @param array $valid_values List of valid values | |||
* @param bool $case_sensitive Whether or not case sensitive | |||
*/ | |||
public function __construct($valid_values = array(), $case_sensitive = false) | |||
{ | |||
$this->valid_values = array_flip($valid_values); | |||
$this->case_sensitive = $case_sensitive; | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$string = trim($string); | |||
if (!$this->case_sensitive) { | |||
// we may want to do full case-insensitive libraries | |||
$string = ctype_lower($string) ? $string : strtolower($string); | |||
} | |||
$result = isset($this->valid_values[$string]); | |||
return $result ? $string : false; | |||
} | |||
/** | |||
* @param string $string In form of comma-delimited list of case-insensitive | |||
* valid values. Example: "foo,bar,baz". Prepend "s:" to make | |||
* case sensitive | |||
* @return HTMLPurifier_AttrDef_Enum | |||
*/ | |||
public function make($string) | |||
{ | |||
if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') { | |||
$string = substr($string, 2); | |||
$sensitive = true; | |||
} else { | |||
$sensitive = false; | |||
} | |||
$values = explode(',', $string); | |||
return new HTMLPurifier_AttrDef_Enum($values, $sensitive); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,48 @@ | |||
<?php | |||
/** | |||
* Validates a boolean attribute | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type string | |||
*/ | |||
protected $name; | |||
/** | |||
* @type bool | |||
*/ | |||
public $minimized = true; | |||
/** | |||
* @param bool|string $name | |||
*/ | |||
public function __construct($name = false) | |||
{ | |||
$this->name = $name; | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
return $this->name; | |||
} | |||
/** | |||
* @param string $string Name of attribute | |||
* @return HTMLPurifier_AttrDef_HTML_Bool | |||
*/ | |||
public function make($string) | |||
{ | |||
return new HTMLPurifier_AttrDef_HTML_Bool($string); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,48 @@ | |||
<?php | |||
/** | |||
* Implements special behavior for class attribute (normally NMTOKENS) | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens | |||
{ | |||
/** | |||
* @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 | |||
$name = $config->getDefinition('HTML')->doctype->name; | |||
if ($name == "XHTML 1.1" || $name == "XHTML 2.0") { | |||
return parent::split($string, $config, $context); | |||
} else { | |||
return preg_split('/\s+/', $string); | |||
} | |||
} | |||
/** | |||
* @param array $tokens | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return array | |||
*/ | |||
protected function filter($tokens, $config, $context) | |||
{ | |||
$allowed = $config->get('Attr.AllowedClasses'); | |||
$forbidden = $config->get('Attr.ForbiddenClasses'); | |||
$ret = array(); | |||
foreach ($tokens as $token) { | |||
if (($allowed === null || isset($allowed[$token])) && | |||
!isset($forbidden[$token]) && | |||
// We need this O(n) check because of PHP's array | |||
// implementation that casts -0 to 0. | |||
!in_array($token, $ret, true) | |||
) { | |||
$ret[] = $token; | |||
} | |||
} | |||
return $ret; | |||
} | |||
} |
@@ -0,0 +1,51 @@ | |||
<?php | |||
/** | |||
* Validates a color according to the HTML spec. | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
static $colors = null; | |||
if ($colors === null) { | |||
$colors = $config->get('Core.ColorKeywords'); | |||
} | |||
$string = trim($string); | |||
if (empty($string)) { | |||
return false; | |||
} | |||
$lower = strtolower($string); | |||
if (isset($colors[$lower])) { | |||
return $colors[$lower]; | |||
} | |||
if ($string[0] === '#') { | |||
$hex = substr($string, 1); | |||
} else { | |||
$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 |
@@ -0,0 +1,16 @@ | |||
<?php | |||
class HTMLPurifier_AttrDef_HTML_ContentEditable extends HTMLPurifier_AttrDef | |||
{ | |||
public function validate($string, $config, $context) | |||
{ | |||
$allowed = array('false'); | |||
if ($config->get('HTML.Trusted')) { | |||
$allowed = array('', 'true', 'false'); | |||
} | |||
$enum = new HTMLPurifier_AttrDef_Enum($allowed); | |||
return $enum->validate($string, $config, $context); | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
<?php | |||
/** | |||
* Special-case enum attribute definition that lazy loads allowed frame targets | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum | |||
{ | |||
/** | |||
* @type array | |||
*/ | |||
public $valid_values = false; // uninitialized value | |||
/** | |||
* @type bool | |||
*/ | |||
protected $case_sensitive = false; | |||
public function __construct() | |||
{ | |||
} | |||
/** | |||
* @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 |
@@ -0,0 +1,113 @@ | |||
<?php | |||
/** | |||
* Validates the HTML attribute ID. | |||
* @warning Even though this is the id processor, it | |||
* will ignore the directive Attr:IDBlacklist, since it will only | |||
* go according to the ID accumulator. Since the accumulator is | |||
* automatically generated, it will have already absorbed the | |||
* blacklist. If you're hacking around, make sure you use load()! | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef | |||
{ | |||
// selector is NOT a valid thing to use for IDREFs, because IDREFs | |||
// *must* target IDs that exist, whereas selector #ids do not. | |||
/** | |||
* Determines whether or not we're validating an ID in a CSS | |||
* selector context. | |||
* @type bool | |||
*/ | |||
protected $selector; | |||
/** | |||
* @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 | |||
if ($id === '') { | |||
return false; | |||
} | |||
$prefix = $config->get('Attr.IDPrefix'); | |||
if ($prefix !== '') { | |||
$prefix .= $config->get('Attr.IDPrefixLocal'); | |||
// prevent re-appending the prefix | |||
if (strpos($id, $prefix) !== 0) { | |||
$id = $prefix . $id; | |||
} | |||
} elseif ($config->get('Attr.IDPrefixLocal') !== '') { | |||
trigger_error( | |||
'%Attr.IDPrefixLocal cannot be used unless ' . | |||
'%Attr.IDPrefix is set', | |||
E_USER_WARNING | |||
); | |||
} | |||
if (!$this->selector) { | |||
$id_accumulator =& $context->get('IDAccumulator'); | |||
if (isset($id_accumulator->ids[$id])) { | |||
return false; | |||
} | |||
} | |||
// we purposely avoid using regex, hopefully this is faster | |||
if ($config->get('Attr.ID.HTML5') === true) { | |||
if (preg_match('/[\t\n\x0b\x0c ]/', $id)) { | |||
return false; | |||
} | |||
} else { | |||
if (ctype_alpha($id)) { | |||
// OK | |||
} else { | |||
if (!ctype_alpha(@$id[0])) { | |||
return false; | |||
} | |||
// primitive style of regexps, I suppose | |||
$trim = trim( | |||
$id, | |||
'A..Za..z0..9:-._' | |||
); | |||
if ($trim !== '') { | |||
return false; | |||
} | |||
} | |||
} | |||
$regexp = $config->get('Attr.IDBlacklistRegexp'); | |||
if ($regexp && preg_match($regexp, $id)) { | |||
return false; | |||
} | |||
if (!$this->selector) { | |||
$id_accumulator->add($id); | |||
} | |||
// if no change was made to the ID, return the result | |||
// else, return the new id if stripping whitespace made it | |||
// valid, or return false. | |||
return $id; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,56 @@ | |||
<?php | |||
/** | |||
* Validates the HTML type length (not to be confused with CSS's length). | |||
* | |||
* This accepts integer pixels or percentages as lengths for certain | |||
* HTML attributes. | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels | |||
{ | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$string = trim($string); | |||
if ($string === '') { | |||
return false; | |||
} | |||
$parent_result = parent::validate($string, $config, $context); | |||
if ($parent_result !== false) { | |||
return $parent_result; | |||
} | |||
$length = strlen($string); | |||
$last_char = $string[$length - 1]; | |||
if ($last_char !== '%') { | |||
return false; | |||
} | |||
$points = substr($string, 0, $length - 1); | |||
if (!is_numeric($points)) { | |||
return false; | |||
} | |||
$points = (int)$points; | |||
if ($points < 0) { | |||
return '0%'; | |||
} | |||
if ($points > 100) { | |||
return '100%'; | |||
} | |||
return ((string)$points) . '%'; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,72 @@ | |||
<?php | |||
/** | |||
* Validates a rel/rev link attribute against a directive of allowed values | |||
* @note We cannot use Enum because link types allow multiple | |||
* values. | |||
* @note Assumes link types are ASCII text | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Name config attribute to pull. | |||
* @type string | |||
*/ | |||
protected $name; | |||
/** | |||
* @param string $name | |||
*/ | |||
public function __construct($name) | |||
{ | |||
$configLookup = array( | |||
'rel' => 'AllowedRel', | |||
'rev' => 'AllowedRev' | |||
); | |||
if (!isset($configLookup[$name])) { | |||
trigger_error( | |||
'Unrecognized attribute name for link ' . | |||
'relationship.', | |||
E_USER_ERROR | |||
); | |||
return; | |||
} | |||
$this->name = $configLookup[$name]; | |||
} | |||
/** | |||
* @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); | |||
if (empty($allowed)) { | |||
return false; | |||
} | |||
$string = $this->parseCDATA($string); | |||
$parts = explode(' ', $string); | |||
// lookup to prevent duplicates | |||
$ret_lookup = array(); | |||
foreach ($parts as $part) { | |||
$part = strtolower(trim($part)); | |||
if (!isset($allowed[$part])) { | |||
continue; | |||
} | |||
$ret_lookup[$part] = true; | |||
} | |||
if (empty($ret_lookup)) { | |||
return false; | |||
} | |||
$string = implode(' ', array_keys($ret_lookup)); | |||
return $string; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,60 @@ | |||
<?php | |||
/** | |||
* Validates a MultiLength as defined by the HTML spec. | |||
* | |||
* A multilength is either a integer (pixel count), a percentage, or | |||
* a relative number. | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length | |||
{ | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$string = trim($string); | |||
if ($string === '') { | |||
return false; | |||
} | |||
$parent_result = parent::validate($string, $config, $context); | |||
if ($parent_result !== false) { | |||
return $parent_result; | |||
} | |||
$length = strlen($string); | |||
$last_char = $string[$length - 1]; | |||
if ($last_char !== '*') { | |||
return false; | |||
} | |||
$int = substr($string, 0, $length - 1); | |||
if ($int == '') { | |||
return '*'; | |||
} | |||
if (!is_numeric($int)) { | |||
return false; | |||
} | |||
$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 |
@@ -0,0 +1,70 @@ | |||
<?php | |||
/** | |||
* Validates contents based on NMTOKENS attribute type. | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_Nmtokens 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; | |||
} | |||
$tokens = $this->split($string, $config, $context); | |||
$tokens = $this->filter($tokens, $config, $context); | |||
if (empty($tokens)) { | |||
return false; | |||
} | |||
return implode(' ', $tokens); | |||
} | |||
/** | |||
* 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) | |||
{ | |||
// OPTIMIZABLE! | |||
// do the preg_match, capture all subpatterns for reformulation | |||
// we don't support U+00A1 and up codepoints or | |||
// escaping because I don't know how to do that with regexps | |||
// and plus it would complicate optimization efforts (you never | |||
// see that anyway). | |||
$pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start | |||
'((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' . | |||
'(?:(?=\s)|\z)/'; // look ahead for space or string end | |||
preg_match_all($pattern, $string, $matches); | |||
return $matches[1]; | |||
} | |||
/** | |||
* Template method for removing certain tokens based on arbitrary criteria. | |||
* @note If we wanted to be really functional, we'd do an array_filter | |||
* 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) | |||
{ | |||
return $tokens; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,76 @@ | |||
<?php | |||
/** | |||
* Validates an integer representation of pixels according to the HTML spec. | |||
*/ | |||
class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type int | |||
*/ | |||
protected $max; | |||
/** | |||
* @param int $max | |||
*/ | |||
public function __construct($max = null) | |||
{ | |||
$this->max = $max; | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$string = trim($string); | |||
if ($string === '0') { | |||
return $string; | |||
} | |||
if ($string === '') { | |||
return false; | |||
} | |||
$length = strlen($string); | |||
if (substr($string, $length - 2) == 'px') { | |||
$string = substr($string, 0, $length - 2); | |||
} | |||
if (!is_numeric($string)) { | |||
return false; | |||
} | |||
$int = (int)$string; | |||
if ($int < 0) { | |||
return '0'; | |||
} | |||
// upper-bound value, extremely high values can | |||
// crash operating systems, see <http://ha.ckers.org/imagecrash.html> | |||
// WARNING, above link WILL crash you if you're using Windows | |||
if ($this->max !== null && $int > $this->max) { | |||
return (string)$this->max; | |||
} | |||
return (string)$int; | |||
} | |||
/** | |||
* @param string $string | |||
* @return HTMLPurifier_AttrDef | |||
*/ | |||
public function make($string) | |||
{ | |||
if ($string === '') { | |||
$max = null; | |||
} else { | |||
$max = (int)$string; | |||
} | |||
$class = get_class($this); | |||
return new $class($max); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,91 @@ | |||
<?php | |||
/** | |||
* Validates an integer. | |||
* @note While this class was modeled off the CSS definition, no currently | |||
* allowed CSS uses this type. The properties that do are: widows, | |||
* orphans, z-index, counter-increment, counter-reset. Some of the | |||
* HTML attributes, however, find use for a non-negative version of this. | |||
*/ | |||
class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Whether or not negative values are allowed. | |||
* @type bool | |||
*/ | |||
protected $negative = true; | |||
/** | |||
* Whether or not zero is allowed. | |||
* @type bool | |||
*/ | |||
protected $zero = true; | |||
/** | |||
* Whether or not positive values are allowed. | |||
* @type bool | |||
*/ | |||
protected $positive = true; | |||
/** | |||
* @param $negative Bool indicating whether or not negative values are allowed | |||
* @param $zero Bool indicating whether or not zero is allowed | |||
* @param $positive Bool indicating whether or not positive values are allowed | |||
*/ | |||
public function __construct($negative = true, $zero = true, $positive = true) | |||
{ | |||
$this->negative = $negative; | |||
$this->zero = $zero; | |||
$this->positive = $positive; | |||
} | |||
/** | |||
* @param string $integer | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($integer, $config, $context) | |||
{ | |||
$integer = $this->parseCDATA($integer); | |||
if ($integer === '') { | |||
return false; | |||
} | |||
// we could possibly simply typecast it to integer, but there are | |||
// certain fringe cases that must not return an integer. | |||
// clip leading sign | |||
if ($this->negative && $integer[0] === '-') { | |||
$digits = substr($integer, 1); | |||
if ($digits === '0') { | |||
$integer = '0'; | |||
} // rm minus sign for zero | |||
} elseif ($this->positive && $integer[0] === '+') { | |||
$digits = $integer = substr($integer, 1); // rm unnecessary plus | |||
} else { | |||
$digits = $integer; | |||
} | |||
// test if it's numeric | |||
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; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,86 @@ | |||
<?php | |||
/** | |||
* Validates the HTML attribute lang, effectively a language code. | |||
* @note Built according to RFC 3066, which obsoleted RFC 1766 | |||
*/ | |||
class HTMLPurifier_AttrDef_Lang 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); | |||
if (!$string) { | |||
return false; | |||
} | |||
$subtags = explode('-', $string); | |||
$num_subtags = count($subtags); | |||
if ($num_subtags == 0) { // sanity check | |||
return false; | |||
} | |||
// process primary subtag : $subtags[0] | |||
$length = strlen($subtags[0]); | |||
switch ($length) { | |||
case 0: | |||
return false; | |||
case 1: | |||
if (!($subtags[0] == 'x' || $subtags[0] == 'i')) { | |||
return false; | |||
} | |||
break; | |||
case 2: | |||
case 3: | |||
if (!ctype_alpha($subtags[0])) { | |||
return false; | |||
} elseif (!ctype_lower($subtags[0])) { | |||
$subtags[0] = strtolower($subtags[0]); | |||
} | |||
break; | |||
default: | |||
return false; | |||
} | |||
$new_string = $subtags[0]; | |||
if ($num_subtags == 1) { | |||
return $new_string; | |||
} | |||
// process second subtag : $subtags[1] | |||
$length = strlen($subtags[1]); | |||
if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) { | |||
return $new_string; | |||
} | |||
if (!ctype_lower($subtags[1])) { | |||
$subtags[1] = strtolower($subtags[1]); | |||
} | |||
$new_string .= '-' . $subtags[1]; | |||
if ($num_subtags == 2) { | |||
return $new_string; | |||
} | |||
// process all other subtags, index 2 and up | |||
for ($i = 2; $i < $num_subtags; $i++) { | |||
$length = strlen($subtags[$i]); | |||
if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) { | |||
return $new_string; | |||
} | |||
if (!ctype_lower($subtags[$i])) { | |||
$subtags[$i] = strtolower($subtags[$i]); | |||
} | |||
$new_string .= '-' . $subtags[$i]; | |||
} | |||
return $new_string; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,53 @@ | |||
<?php | |||
/** | |||
* Decorator that, depending on a token, switches between two definitions. | |||
*/ | |||
class HTMLPurifier_AttrDef_Switch | |||
{ | |||
/** | |||
* @type string | |||
*/ | |||
protected $tag; | |||
/** | |||
* @type HTMLPurifier_AttrDef | |||
*/ | |||
protected $withTag; | |||
/** | |||
* @type HTMLPurifier_AttrDef | |||
*/ | |||
protected $withoutTag; | |||
/** | |||
* @param string $tag Tag name to switch upon | |||
* @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 | |||
*/ | |||
public function __construct($tag, $with_tag, $without_tag) | |||
{ | |||
$this->tag = $tag; | |||
$this->withTag = $with_tag; | |||
$this->withoutTag = $without_tag; | |||
} | |||
/** | |||
* @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); | |||
if (!$token || $token->name !== $this->tag) { | |||
return $this->withoutTag->validate($string, $config, $context); | |||
} else { | |||
return $this->withTag->validate($string, $config, $context); | |||
} | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,21 @@ | |||
<?php | |||
/** | |||
* Validates arbitrary text according to the HTML spec. | |||
*/ | |||
class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
return $this->parseCDATA($string); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,111 @@ | |||
<?php | |||
/** | |||
* Validates a URI as defined by RFC 3986. | |||
* @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme | |||
*/ | |||
class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* @type HTMLPurifier_URIParser | |||
*/ | |||
protected $parser; | |||
/** | |||
* @type bool | |||
*/ | |||
protected $embedsResource; | |||
/** | |||
* @param bool $embeds_resource Does the URI here result in an extra HTTP request? | |||
*/ | |||
public function __construct($embeds_resource = false) | |||
{ | |||
$this->parser = new HTMLPurifier_URIParser(); | |||
$this->embedsResource = (bool)$embeds_resource; | |||
} | |||
/** | |||
* @param string $string | |||
* @return HTMLPurifier_AttrDef_URI | |||
*/ | |||
public function make($string) | |||
{ | |||
$embeds = ($string === 'embedded'); | |||
return new HTMLPurifier_AttrDef_URI($embeds); | |||
} | |||
/** | |||
* @param string $uri | |||
* @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); | |||
// parse the URI | |||
$uri = $this->parser->parse($uri); | |||
if ($uri === false) { | |||
return false; | |||
} | |||
// add embedded flag to context for validators | |||
$context->register('EmbeddedURI', $this->embedsResource); | |||
$ok = false; | |||
do { | |||
// generic validation | |||
$result = $uri->validate($config, $context); | |||
if (!$result) { | |||
break; | |||
} | |||
// chained filtering | |||
$uri_def = $config->getDefinition('URI'); | |||
$result = $uri_def->filter($uri, $config, $context); | |||
if (!$result) { | |||
break; | |||
} | |||
// scheme-specific validation | |||
$scheme_obj = $uri->getSchemeObj($config, $context); | |||
if (!$scheme_obj) { | |||
break; | |||
} | |||
if ($this->embedsResource && !$scheme_obj->browsable) { | |||
break; | |||
} | |||
$result = $scheme_obj->validate($uri, $config, $context); | |||
if (!$result) { | |||
break; | |||
} | |||
// Post chained filtering | |||
$result = $uri_def->postFilter($uri, $config, $context); | |||
if (!$result) { | |||
break; | |||
} | |||
// survived gauntlet | |||
$ok = true; | |||
} while (false); | |||
$context->destroy('EmbeddedURI'); | |||
if (!$ok) { | |||
return false; | |||
} | |||
// back to string | |||
return $uri->toString(); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,20 @@ | |||
<?php | |||
abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* Unpacks a mailbox into its display-name and address | |||
* @param string $string | |||
* @return mixed | |||
*/ | |||
public function unpack($string) | |||
{ | |||
// needs to be implemented | |||
} | |||
} | |||
// sub-implementations | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,29 @@ | |||
<?php | |||
/** | |||
* Primitive email validation class based on the regexp found at | |||
* http://www.regular-expressions.info/email.html | |||
*/ | |||
class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email | |||
{ | |||
/** | |||
* @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>" | |||
// that needs more percent encoding to be done | |||
if ($string == '') { | |||
return false; | |||
} | |||
$string = trim($string); | |||
$result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string); | |||
return $result ? $string : false; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,142 @@ | |||
<?php | |||
/** | |||
* Validates a host according to the IPv4, IPv6 and DNS (future) specifications. | |||
*/ | |||
class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* IPv4 sub-validator. | |||
* @type HTMLPurifier_AttrDef_URI_IPv4 | |||
*/ | |||
protected $ipv4; | |||
/** | |||
* IPv6 sub-validator. | |||
* @type HTMLPurifier_AttrDef_URI_IPv6 | |||
*/ | |||
protected $ipv6; | |||
public function __construct() | |||
{ | |||
$this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4(); | |||
$this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6(); | |||
} | |||
/** | |||
* @param string $string | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($string, $config, $context) | |||
{ | |||
$length = strlen($string); | |||
// empty hostname is OK; it's usually semantically equivalent: | |||
// 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 | |||
$ip = substr($string, 1, $length - 2); | |||
$valid = $this->ipv6->validate($ip, $config, $context); | |||
if ($valid === false) { | |||
return false; | |||
} | |||
return '[' . $valid . ']'; | |||
} | |||
// need to do checks on unusual encodings too | |||
$ipv4 = $this->ipv4->validate($string, $config, $context); | |||
if ($ipv4 !== false) { | |||
return $ipv4; | |||
} | |||
// A regular domain name. | |||
// This doesn't match I18N domain names, but we don't have proper IRI support, | |||
// so force users to insert Punycode. | |||
// 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') ? '_' : ''; | |||
// Based off of RFC 1738, but amended so that | |||
// as per RFC 3696, the top label need only not be all numeric. | |||
// The productions describing this are: | |||
$a = '[a-z]'; // alpha | |||
$an = '[a-z0-9]'; // alphanum | |||
$and = "[a-z0-9-$underscore]"; // alphanum | "-" | |||
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum | |||
$domainlabel = "$an(?:$and*$an)?"; | |||
// AMENDED as per RFC 3696 | |||
// toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum | |||
// side condition: not all numeric | |||
$toplabel = "$an(?:$and*$an)?"; | |||
// hostname = *( domainlabel "." ) toplabel [ "." ] | |||
if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) { | |||
if (!ctype_digit($matches[1])) { | |||
return $string; | |||
} | |||
} | |||
// PHP 5.3 and later support this functionality natively | |||
if (function_exists('idn_to_ascii')) { | |||
if (defined('IDNA_NONTRANSITIONAL_TO_ASCII') && defined('INTL_IDNA_VARIANT_UTS46')) { | |||
$string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); | |||
} else { | |||
$string = idn_to_ascii($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 | |||
} elseif ($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); | |||
} catch (Exception $e) { | |||
// XXX error reporting | |||
} | |||
} | |||
// Try again | |||
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { | |||
return $string; | |||
} | |||
return false; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,45 @@ | |||
<?php | |||
/** | |||
* Validates an IPv4 address | |||
* @author Feyd @ forums.devnetwork.net (public domain) | |||
*/ | |||
class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef | |||
{ | |||
/** | |||
* IPv4 regex, protected so that IPv6 can reuse it. | |||
* @type string | |||
*/ | |||
protected $ip4; | |||
/** | |||
* @param string $aIP | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($aIP, $config, $context) | |||
{ | |||
if (!$this->ip4) { | |||
$this->_loadRegex(); | |||
} | |||
if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) { | |||
return $aIP; | |||
} | |||
return false; | |||
} | |||
/** | |||
* Lazy load function to prevent regex from being stuffed in | |||
* cache. | |||
*/ | |||
protected function _loadRegex() | |||
{ | |||
$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})"; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,89 @@ | |||
<?php | |||
/** | |||
* Validates an IPv6 address. | |||
* @author Feyd @ forums.devnetwork.net (public domain) | |||
* @note This function requires brackets to have been removed from address | |||
* in URI. | |||
*/ | |||
class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4 | |||
{ | |||
/** | |||
* @param string $aIP | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool|string | |||
*/ | |||
public function validate($aIP, $config, $context) | |||
{ | |||
if (!$this->ip4) { | |||
$this->_loadRegex(); | |||
} | |||
$original = $aIP; | |||
$hex = '[0-9a-fA-F]'; | |||
$blk = '(?:' . $hex . '{1,4})'; | |||
$pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128 | |||
// prefix check | |||
if (strpos($aIP, '/') !== false) { | |||
if (preg_match('#' . $pre . '$#s', $aIP, $find)) { | |||
$aIP = substr($aIP, 0, 0 - strlen($find[0])); | |||
unset($find); | |||
} else { | |||
return false; | |||
} | |||
} | |||
// IPv4-compatiblity check | |||
if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) { | |||
$aIP = substr($aIP, 0, 0 - strlen($find[0])); | |||
$ip = explode('.', $find[0]); | |||
$ip = array_map('dechex', $ip); | |||
$aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3]; | |||
unset($find, $ip); | |||
} | |||
// compression check | |||
$aIP = explode('::', $aIP); | |||
$c = count($aIP); | |||
if ($c > 2) { | |||
return false; | |||
} elseif ($c == 2) { | |||
list($first, $second) = $aIP; | |||
$first = explode(':', $first); | |||
$second = explode(':', $second); | |||
if (count($first) + count($second) > 8) { | |||
return false; | |||
} | |||
while (count($first) < 8) { | |||
array_push($first, '0'); | |||
} | |||
array_splice($first, 8 - count($second), 8, $second); | |||
$aIP = $first; | |||
unset($first, $second); | |||
} else { | |||
$aIP = explode(':', $aIP[0]); | |||
} | |||
$c = count($aIP); | |||
if ($c != 8) { | |||
return false; | |||
} | |||
// All the pieces should be 16-bit hex strings. Are they? | |||
foreach ($aIP as $piece) { | |||
if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) { | |||
return false; | |||
} | |||
} | |||
return $original; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,60 @@ | |||
<?php | |||
/** | |||
* Processes an entire attribute array for corrections needing multiple values. | |||
* | |||
* Occasionally, a certain attribute will need to be removed and popped onto | |||
* another value. Instead of creating a complex return syntax for | |||
* HTMLPurifier_AttrDef, we just pass the whole attribute array to a | |||
* specialized object and have that do the special work. That is the | |||
* family of HTMLPurifier_AttrTransform. | |||
* | |||
* An attribute transformation can be assigned to run before or after | |||
* HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for | |||
* more details. | |||
*/ | |||
abstract class HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* Abstract: makes changes to the attributes dependent on multiple values. | |||
* | |||
* @param array $attr Assoc array of attributes, usually from | |||
* HTMLPurifier_Token_Tag::$attr | |||
* @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object. | |||
* @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object | |||
* @return array Processed attribute array. | |||
*/ | |||
abstract public function transform($attr, $config, $context); | |||
/** | |||
* Prepends CSS properties to the style attribute, creating the | |||
* attribute if it doesn't exist. | |||
* @param array &$attr Attribute array to process (passed by reference) | |||
* @param string $css CSS to prepend | |||
*/ | |||
public function prependCSS(&$attr, $css) | |||
{ | |||
$attr['style'] = isset($attr['style']) ? $attr['style'] : ''; | |||
$attr['style'] = $css . $attr['style']; | |||
} | |||
/** | |||
* Retrieves and removes an attribute | |||
* @param array &$attr Attribute array to process (passed by reference) | |||
* @param mixed $key Key of attribute to confiscate | |||
* @return mixed | |||
*/ | |||
public function confiscateAttr(&$attr, $key) | |||
{ | |||
if (!isset($attr[$key])) { | |||
return null; | |||
} | |||
$value = $attr[$key]; | |||
unset($attr[$key]); | |||
return $value; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,28 @@ | |||
<?php | |||
/** | |||
* Pre-transform that changes proprietary background attribute to CSS. | |||
*/ | |||
class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @param array $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'); | |||
// some validation should happen here | |||
$this->prependCSS($attr, "background-image:url($background);"); | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,27 @@ | |||
<?php | |||
// this MUST be placed in post, as it assumes that any value in dir is valid | |||
/** | |||
* Post-trasnform that ensures that bdo tags have the dir attribute set. | |||
*/ | |||
class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @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'); | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,28 @@ | |||
<?php | |||
/** | |||
* Pre-transform that changes deprecated bgcolor attribute to CSS. | |||
*/ | |||
class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @param array $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'); | |||
// some validation should happen here | |||
$this->prependCSS($attr, "background-color:$bgcolor;"); | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,47 @@ | |||
<?php | |||
/** | |||
* Pre-transform that changes converts a boolean attribute to fixed CSS | |||
*/ | |||
class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* Name of boolean attribute that is trigger. | |||
* @type string | |||
*/ | |||
protected $attr; | |||
/** | |||
* CSS declarations to add to style, needs trailing semicolon. | |||
* @type string | |||
*/ | |||
protected $css; | |||
/** | |||
* @param string $attr attribute name to convert from | |||
* @param string $css CSS declarations to add to style (needs semicolon) | |||
*/ | |||
public function __construct($attr, $css) | |||
{ | |||
$this->attr = $attr; | |||
$this->css = $css; | |||
} | |||
/** | |||
* @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]); | |||
$this->prependCSS($attr, $this->css); | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,26 @@ | |||
<?php | |||
/** | |||
* Pre-transform that changes deprecated border attribute to CSS. | |||
*/ | |||
class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @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'); | |||
// some validation should happen here | |||
$this->prependCSS($attr, "border:{$border_width}px solid;"); | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,68 @@ | |||
<?php | |||
/** | |||
* Generic pre-transform that converts an attribute with a fixed number of | |||
* values (enumerated) to CSS. | |||
*/ | |||
class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* Name of attribute to transform from. | |||
* @type string | |||
*/ | |||
protected $attr; | |||
/** | |||
* Lookup array of attribute values to CSS. | |||
* @type array | |||
*/ | |||
protected $enumToCSS = array(); | |||
/** | |||
* Case sensitivity of the matching. | |||
* @type bool | |||
* @warning Currently can only be guaranteed to work with ASCII | |||
* values. | |||
*/ | |||
protected $caseSensitive = false; | |||
/** | |||
* @param string $attr Attribute name to transform from | |||
* @param array $enum_to_css Lookup array of attribute values to CSS | |||
* @param bool $case_sensitive Case sensitivity indicator, default false | |||
*/ | |||
public function __construct($attr, $enum_to_css, $case_sensitive = false) | |||
{ | |||
$this->attr = $attr; | |||
$this->enumToCSS = $enum_to_css; | |||
$this->caseSensitive = (bool)$case_sensitive; | |||
} | |||
/** | |||
* @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; | |||
} | |||
$value = trim($attr[$this->attr]); | |||
unset($attr[$this->attr]); | |||
if (!$this->caseSensitive) { | |||
$value = strtolower($value); | |||
} | |||
if (!isset($this->enumToCSS[$value])) { | |||
return $attr; | |||
} | |||
$this->prependCSS($attr, $this->enumToCSS[$value]); | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,47 @@ | |||
<?php | |||
// must be called POST validation | |||
/** | |||
* Transform that supplies default values for the src and alt attributes | |||
* in img tags, as well as prevents the img tag from being removed | |||
* because of a missing alt tag. This needs to be registered as both | |||
* a pre and post attribute transform. | |||
*/ | |||
class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @param array $attr | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return array | |||
*/ | |||
public function transform($attr, $config, $context) | |||
{ | |||
$src = true; | |||
if (!isset($attr['src'])) { | |||
if ($config->get('Core.RemoveInvalidImg')) { | |||
return $attr; | |||
} | |||
$attr['src'] = $config->get('Attr.DefaultInvalidImage'); | |||
$src = false; | |||
} | |||
if (!isset($attr['alt'])) { | |||
if ($src) { | |||
$alt = $config->get('Attr.DefaultImageAlt'); | |||
if ($alt === null) { | |||
$attr['alt'] = basename($attr['src']); | |||
} else { | |||
$attr['alt'] = $alt; | |||
} | |||
} else { | |||
$attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt'); | |||
} | |||
} | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,61 @@ | |||
<?php | |||
/** | |||
* Pre-transform that changes deprecated hspace and vspace attributes to CSS | |||
*/ | |||
class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @type string | |||
*/ | |||
protected $attr; | |||
/** | |||
* @type array | |||
*/ | |||
protected $css = array( | |||
'hspace' => array('left', 'right'), | |||
'vspace' => array('top', 'bottom') | |||
); | |||
/** | |||
* @param string $attr | |||
*/ | |||
public function __construct($attr) | |||
{ | |||
$this->attr = $attr; | |||
if (!isset($this->css[$attr])) { | |||
trigger_error(htmlspecialchars($attr) . ' is not valid space attribute'); | |||
} | |||
} | |||
/** | |||
* @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; | |||
} | |||
$width = $this->confiscateAttr($attr, $this->attr); | |||
// some validation could happen here | |||
if (!isset($this->css[$this->attr])) { | |||
return $attr; | |||
} | |||
$style = ''; | |||
foreach ($this->css[$this->attr] as $suffix) { | |||
$property = "margin-$suffix"; | |||
$style .= "$property:{$width}px;"; | |||
} | |||
$this->prependCSS($attr, $style); | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,56 @@ | |||
<?php | |||
/** | |||
* Performs miscellaneous cross attribute validation and filtering for | |||
* input elements. This is meant to be a post-transform. | |||
*/ | |||
class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @type HTMLPurifier_AttrDef_HTML_Pixels | |||
*/ | |||
protected $pixels; | |||
public function __construct() | |||
{ | |||
$this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels(); | |||
} | |||
/** | |||
* @param array $attr | |||
* @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') { | |||
unset($attr['checked']); | |||
} | |||
if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') { | |||
unset($attr['maxlength']); | |||
} | |||
if (isset($attr['size']) && $t !== 'text' && $t !== 'password') { | |||
$result = $this->pixels->validate($attr['size'], $config, $context); | |||
if ($result === false) { | |||
unset($attr['size']); | |||
} else { | |||
$attr['size'] = $result; | |||
} | |||
} | |||
if (isset($attr['src']) && $t !== 'image') { | |||
unset($attr['src']); | |||
} | |||
if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) { | |||
$attr['value'] = ''; | |||
} | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,31 @@ | |||
<?php | |||
/** | |||
* Post-transform that copies lang's value to xml:lang (and vice-versa) | |||
* @note Theoretically speaking, this could be a pre-transform, but putting | |||
* post is more efficient. | |||
*/ | |||
class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @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; | |||
$xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false; | |||
if ($lang !== false && $xml_lang === false) { | |||
$attr['xml:lang'] = $lang; | |||
} elseif ($xml_lang !== false) { | |||
$attr['lang'] = $xml_lang; | |||
} | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,45 @@ | |||
<?php | |||
/** | |||
* Class for handling width/height length attribute transformations to CSS | |||
*/ | |||
class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @type string | |||
*/ | |||
protected $name; | |||
/** | |||
* @type string | |||
*/ | |||
protected $cssName; | |||
public function __construct($name, $css_name = null) | |||
{ | |||
$this->name = $name; | |||
$this->cssName = $css_name ? $css_name : $name; | |||
} | |||
/** | |||
* @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); | |||
if (ctype_digit($length)) { | |||
$length .= 'px'; | |||
} | |||
$this->prependCSS($attr, $this->cssName . ":$length;"); | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,33 @@ | |||
<?php | |||
/** | |||
* Pre-transform that changes deprecated name attribute to ID if necessary | |||
*/ | |||
class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @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 | |||
if ($config->get('HTML.Attr.Name.UseCDATA')) { | |||
return $attr; | |||
} | |||
if (!isset($attr['name'])) { | |||
return $attr; | |||
} | |||
$id = $this->confiscateAttr($attr, 'name'); | |||
if (isset($attr['id'])) { | |||
return $attr; | |||
} | |||
$attr['id'] = $id; | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,46 @@ | |||
<?php | |||
/** | |||
* Post-transform that performs validation to the name attribute; if | |||
* it is present with an equivalent id attribute, it is passed through; | |||
* otherwise validation is performed. | |||
*/ | |||
class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @type HTMLPurifier_AttrDef_HTML_ID | |||
*/ | |||
public $idDef; | |||
public function __construct() | |||
{ | |||
$this->idDef = new HTMLPurifier_AttrDef_HTML_ID(); | |||
} | |||
/** | |||
* @param array $attr | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return array | |||
*/ | |||
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; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -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 |
@@ -0,0 +1,25 @@ | |||
<?php | |||
class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @type string | |||
*/ | |||
public $name = "SafeEmbed"; | |||
/** | |||
* @param array $attr | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return array | |||
*/ | |||
public function transform($attr, $config, $context) | |||
{ | |||
$attr['allowscriptaccess'] = 'never'; | |||
$attr['allownetworking'] = 'internal'; | |||
$attr['type'] = 'application/x-shockwave-flash'; | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,28 @@ | |||
<?php | |||
/** | |||
* Writes default type for all objects. Currently only supports flash. | |||
*/ | |||
class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @type string | |||
*/ | |||
public $name = "SafeObject"; | |||
/** | |||
* @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; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,84 @@ | |||
<?php | |||
/** | |||
* Validates name/value pairs in param tags to be used in safe objects. This | |||
* will only allow name values it recognizes, and pre-fill certain attributes | |||
* with required values. | |||
* | |||
* @note | |||
* This class only supports Flash. In the future, Quicktime support | |||
* may be added. | |||
* | |||
* @warning | |||
* This class expects an injector to add the necessary parameters tags. | |||
*/ | |||
class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @type string | |||
*/ | |||
public $name = "SafeParam"; | |||
/** | |||
* @type HTMLPurifier_AttrDef_URI | |||
*/ | |||
private $uri; | |||
/** | |||
* @type HTMLPurifier_AttrDef_Enum | |||
*/ | |||
public $wmode; | |||
public function __construct() | |||
{ | |||
$this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded | |||
$this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent')); | |||
} | |||
/** | |||
* @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 | |||
// transforms. | |||
switch ($attr['name']) { | |||
// application/x-shockwave-flash | |||
// Keep this synchronized with Injector/SafeObject.php | |||
case 'allowScriptAccess': | |||
$attr['value'] = 'never'; | |||
break; | |||
case 'allowNetworking': | |||
$attr['value'] = 'internal'; | |||
break; | |||
case 'allowFullScreen': | |||
if ($config->get('HTML.FlashAllowFullScreen')) { | |||
$attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false'; | |||
} else { | |||
$attr['value'] = 'false'; | |||
} | |||
break; | |||
case 'wmode': | |||
$attr['value'] = $this->wmode->validate($attr['value'], $config, $context); | |||
break; | |||
case 'movie': | |||
case 'src': | |||
$attr['name'] = "movie"; | |||
$attr['value'] = $this->uri->validate($attr['value'], $config, $context); | |||
break; | |||
case 'flashvars': | |||
// we're going to allow arbitrary inputs to the SWF, on | |||
// the reasoning that it could only hack the SWF, not us. | |||
break; | |||
// add other cases to support other param name/value pairs | |||
default: | |||
$attr['name'] = $attr['value'] = null; | |||
} | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,23 @@ | |||
<?php | |||
/** | |||
* Implements required attribute stipulation for <script> | |||
*/ | |||
class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @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'] = 'text/javascript'; | |||
} | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -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 |
@@ -0,0 +1,37 @@ | |||
<?php | |||
// must be called POST validation | |||
/** | |||
* Adds rel="noopener" to any links which target a different window | |||
* than the current one. This is used to prevent malicious websites | |||
* from silently replacing the original window, which could be used | |||
* to do phishing. | |||
* This transform is controlled by %HTML.TargetNoopener. | |||
*/ | |||
class HTMLPurifier_AttrTransform_TargetNoopener extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @param array $attr | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return array | |||
*/ | |||
public function transform($attr, $config, $context) | |||
{ | |||
if (isset($attr['rel'])) { | |||
$rels = explode(' ', $attr['rel']); | |||
} else { | |||
$rels = array(); | |||
} | |||
if (isset($attr['target']) && !in_array('noopener', $rels)) { | |||
$rels[] = 'noopener'; | |||
} | |||
if (!empty($rels) || isset($attr['rel'])) { | |||
$attr['rel'] = implode(' ', $rels); | |||
} | |||
return $attr; | |||
} | |||
} | |||
@@ -0,0 +1,37 @@ | |||
<?php | |||
// must be called POST validation | |||
/** | |||
* Adds rel="noreferrer" to any links which target a different window | |||
* than the current one. This is used to prevent malicious websites | |||
* from silently replacing the original window, which could be used | |||
* to do phishing. | |||
* This transform is controlled by %HTML.TargetNoreferrer. | |||
*/ | |||
class HTMLPurifier_AttrTransform_TargetNoreferrer extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @param array $attr | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return array | |||
*/ | |||
public function transform($attr, $config, $context) | |||
{ | |||
if (isset($attr['rel'])) { | |||
$rels = explode(' ', $attr['rel']); | |||
} else { | |||
$rels = array(); | |||
} | |||
if (isset($attr['target']) && !in_array('noreferrer', $rels)) { | |||
$rels[] = 'noreferrer'; | |||
} | |||
if (!empty($rels) || isset($attr['rel'])) { | |||
$attr['rel'] = implode(' ', $rels); | |||
} | |||
return $attr; | |||
} | |||
} | |||
@@ -0,0 +1,27 @@ | |||
<?php | |||
/** | |||
* Sets height/width defaults for <textarea> | |||
*/ | |||
class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform | |||
{ | |||
/** | |||
* @param array $attr | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return array | |||
*/ | |||
public function transform($attr, $config, $context) | |||
{ | |||
// Calculated from Firefox | |||
if (!isset($attr['cols'])) { | |||
$attr['cols'] = '22'; | |||
} | |||
if (!isset($attr['rows'])) { | |||
$attr['rows'] = '3'; | |||
} | |||
return $attr; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,97 @@ | |||
<?php | |||
/** | |||
* Provides lookup array of attribute types to HTMLPurifier_AttrDef objects | |||
*/ | |||
class HTMLPurifier_AttrTypes | |||
{ | |||
/** | |||
* Lookup array of attribute string identifiers to concrete implementations. | |||
* @type HTMLPurifier_AttrDef[] | |||
*/ | |||
protected $info = array(); | |||
/** | |||
* Constructs the info array, supplying default implementations for attribute | |||
* types. | |||
*/ | |||
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 | |||
$this->info['Enum'] = new HTMLPurifier_AttrDef_Enum(); | |||
$this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); | |||
$this->info['CDATA'] = new HTMLPurifier_AttrDef_Text(); | |||
$this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID(); | |||
$this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length(); | |||
$this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength(); | |||
$this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens(); | |||
$this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels(); | |||
$this->info['Text'] = new HTMLPurifier_AttrDef_Text(); | |||
$this->info['URI'] = new HTMLPurifier_AttrDef_URI(); | |||
$this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); | |||
$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(); | |||
$this->info['ContentEditable'] = new HTMLPurifier_AttrDef_HTML_ContentEditable(); | |||
// unimplemented aliases | |||
$this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); | |||
$this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text(); | |||
$this->info['Charsets'] = new HTMLPurifier_AttrDef_Text(); | |||
$this->info['Character'] = new HTMLPurifier_AttrDef_Text(); | |||
// "proprietary" types | |||
$this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class(); | |||
// number is really a positive integer (one or more digits) | |||
// FIXME: ^^ not always, see start and value of list items | |||
$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 | |||
* @param string $type String type name | |||
* @return HTMLPurifier_AttrDef Object AttrDef for type | |||
*/ | |||
public function get($type) | |||
{ | |||
// determine if there is any extra info tacked on | |||
if (strpos($type, '#') !== false) { | |||
list($type, $string) = explode('#', $type, 2); | |||
} else { | |||
$string = ''; | |||
} | |||
if (!isset($this->info[$type])) { | |||
trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR); | |||
return; | |||
} | |||
return $this->info[$type]->make($string); | |||
} | |||
/** | |||
* Sets a new implementation for a type | |||
* @param string $type String type name | |||
* @param HTMLPurifier_AttrDef $impl Object AttrDef for type | |||
*/ | |||
public function set($type, $impl) | |||
{ | |||
$this->info[$type] = $impl; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,178 @@ | |||
<?php | |||
/** | |||
* Validates the attributes of a token. Doesn't manage required attributes | |||
* very well. The only reason we factored this out was because RemoveForeignElements | |||
* also needed it besides ValidateAttributes. | |||
*/ | |||
class HTMLPurifier_AttrValidator | |||
{ | |||
/** | |||
* Validates the attributes of a token, mutating it as necessary. | |||
* that has valid tokens | |||
* @param HTMLPurifier_Token $token Token to validate. | |||
* @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config | |||
* @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context | |||
*/ | |||
public function validateToken($token, $config, $context) | |||
{ | |||
$definition = $config->getHTMLDefinition(); | |||
$e =& $context->get('ErrorCollector', true); | |||
// initialize IDAccumulator if necessary | |||
$ok =& $context->get('IDAccumulator', true); | |||
if (!$ok) { | |||
$id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); | |||
$context->register('IDAccumulator', $id_accumulator); | |||
} | |||
// initialize CurrentToken if necessary | |||
$current_token =& $context->get('CurrentToken', true); | |||
if (!$current_token) { | |||
$context->register('CurrentToken', $token); | |||
} | |||
if (!$token instanceof HTMLPurifier_Token_Start && | |||
!$token instanceof HTMLPurifier_Token_Empty | |||
) { | |||
return; | |||
} | |||
// create alias to global definition array, see also $defs | |||
// DEFINITION CALL | |||
$d_defs = $definition->info_global_attr; | |||
// don't update token until the very end, to ensure an atomic update | |||
$attr = $token->attr; | |||
// do global transformations (pre) | |||
// nothing currently utilizes this | |||
foreach ($definition->info_attr_transform_pre as $transform) { | |||
$attr = $transform->transform($o = $attr, $config, $context); | |||
if ($e) { | |||
if ($attr != $o) { | |||
$e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); | |||
} | |||
} | |||
} | |||
// do local transformations only applicable to this element (pre) | |||
// ex. <p align="right"> to <p style="text-align:right;"> | |||
foreach ($definition->info[$token->name]->attr_transform_pre as $transform) { | |||
$attr = $transform->transform($o = $attr, $config, $context); | |||
if ($e) { | |||
if ($attr != $o) { | |||
$e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); | |||
} | |||
} | |||
} | |||
// create alias to this element's attribute definition array, see | |||
// also $d_defs (global attribute definition array) | |||
// DEFINITION CALL | |||
$defs = $definition->info[$token->name]->attr; | |||
$attr_key = false; | |||
$context->register('CurrentAttr', $attr_key); | |||
// iterate through all the attribute keypairs | |||
// Watch out for name collisions: $key has previously been used | |||
foreach ($attr as $attr_key => $value) { | |||
// call the definition | |||
if (isset($defs[$attr_key])) { | |||
// there is a local definition defined | |||
if ($defs[$attr_key] === false) { | |||
// We've explicitly been told not to allow this element. | |||
// This is usually when there's a global definition | |||
// that must be overridden. | |||
// Theoretically speaking, we could have a | |||
// AttrDef_DenyAll, but this is faster! | |||
$result = false; | |||
} else { | |||
// validate according to the element's definition | |||
$result = $defs[$attr_key]->validate( | |||
$value, | |||
$config, | |||
$context | |||
); | |||
} | |||
} elseif (isset($d_defs[$attr_key])) { | |||
// there is a global definition defined, validate according | |||
// to the global definition | |||
$result = $d_defs[$attr_key]->validate( | |||
$value, | |||
$config, | |||
$context | |||
); | |||
} else { | |||
// system never heard of the attribute? DELETE! | |||
$result = false; | |||
} | |||
// put the results into effect | |||
if ($result === false || $result === null) { | |||
// this is a generic error message that should replaced | |||
// with more specific ones when possible | |||
if ($e) { | |||
$e->send(E_ERROR, 'AttrValidator: Attribute removed'); | |||
} | |||
// remove the attribute | |||
unset($attr[$attr_key]); | |||
} elseif (is_string($result)) { | |||
// generally, if a substitution is happening, there | |||
// was some sort of implicit correction going on. We'll | |||
// delegate it to the attribute classes to say exactly what. | |||
// simple substitution | |||
$attr[$attr_key] = $result; | |||
} else { | |||
// nothing happens | |||
} | |||
// we'd also want slightly more complicated substitution | |||
// involving an array as the return value, | |||
// although we're not sure how colliding attributes would | |||
// resolve (certain ones would be completely overriden, | |||
// others would prepend themselves). | |||
} | |||
$context->destroy('CurrentAttr'); | |||
// post transforms | |||
// global (error reporting untested) | |||
foreach ($definition->info_attr_transform_post as $transform) { | |||
$attr = $transform->transform($o = $attr, $config, $context); | |||
if ($e) { | |||
if ($attr != $o) { | |||
$e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); | |||
} | |||
} | |||
} | |||
// local (error reporting untested) | |||
foreach ($definition->info[$token->name]->attr_transform_post as $transform) { | |||
$attr = $transform->transform($o = $attr, $config, $context); | |||
if ($e) { | |||
if ($attr != $o) { | |||
$e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); | |||
} | |||
} | |||
} | |||
$token->attr = $attr; | |||
// destroy CurrentToken if we made it ourselves | |||
if (!$current_token) { | |||
$context->destroy('CurrentToken'); | |||
} | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,124 @@ | |||
<?php | |||
// constants are slow, so we use as few as possible | |||
if (!defined('HTMLPURIFIER_PREFIX')) { | |||
define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..')); | |||
} | |||
// accomodations for versions earlier than 5.0.2 | |||
// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net> | |||
if (!defined('PHP_EOL')) { | |||
switch (strtoupper(substr(PHP_OS, 0, 3))) { | |||
case 'WIN': | |||
define('PHP_EOL', "\r\n"); | |||
break; | |||
case 'DAR': | |||
define('PHP_EOL', "\r"); | |||
break; | |||
default: | |||
define('PHP_EOL', "\n"); | |||
} | |||
} | |||
/** | |||
* Bootstrap class that contains meta-functionality for HTML Purifier such as | |||
* the autoload function. | |||
* | |||
* @note | |||
* This class may be used without any other files from HTML Purifier. | |||
*/ | |||
class HTMLPurifier_Bootstrap | |||
{ | |||
/** | |||
* Autoload function for HTML Purifier | |||
* @param string $class Class to load | |||
* @return bool | |||
*/ | |||
public static function autoload($class) | |||
{ | |||
$file = HTMLPurifier_Bootstrap::getPath($class); | |||
if (!$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; | |||
} | |||
/** | |||
* Returns the path for a specific class. | |||
* @param string $class Class path to get | |||
* @return string | |||
*/ | |||
public static function getPath($class) | |||
{ | |||
if (strncmp('HTMLPurifier', $class, 12) !== 0) { | |||
return false; | |||
} | |||
// Custom implementations | |||
if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { | |||
$code = str_replace('_', '-', substr($class, 22)); | |||
$file = 'HTMLPurifier/Language/classes/' . $code . '.php'; | |||
} else { | |||
$file = str_replace('_', '/', $class) . '.php'; | |||
} | |||
if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) { | |||
return false; | |||
} | |||
return $file; | |||
} | |||
/** | |||
* "Pre-registers" our autoloader on the SPL stack. | |||
*/ | |||
public static function registerAutoload() | |||
{ | |||
$autoload = array('HTMLPurifier_Bootstrap', 'autoload'); | |||
if (($funcs = spl_autoload_functions()) === false) { | |||
spl_autoload_register($autoload); | |||
} 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', '<=') && | |||
version_compare(PHP_VERSION, '5.1.0', '>='); | |||
foreach ($funcs as $func) { | |||
if ($buggy && is_array($func)) { | |||
// :TRICKY: There are some compatibility issues and some | |||
// places where we need to error out | |||
$reflector = new ReflectionMethod($func[0], $func[1]); | |||
if (!$reflector->isStatic()) { | |||
throw new Exception( | |||
'HTML Purifier autoloader registrar is not compatible | |||
with non-static object methods due to PHP Bug #44144; | |||
Please do not use HTMLPurifier.autoload.php (or any | |||
file that includes this file); instead, place the code: | |||
spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\')) | |||
after your own autoloaders.' | |||
); | |||
} | |||
// Suprisingly, spl_autoload_register supports the | |||
// Class::staticMethod callback format, although call_user_func doesn't | |||
if ($compat) { | |||
$func = implode('::', $func); | |||
} | |||
} | |||
spl_autoload_unregister($func); | |||
} | |||
spl_autoload_register($autoload); | |||
foreach ($funcs as $func) { | |||
spl_autoload_register($func); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,549 @@ | |||
<?php | |||
/** | |||
* Defines allowed CSS attributes and what their values are. | |||
* @see HTMLPurifier_HTMLDefinition | |||
*/ | |||
class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition | |||
{ | |||
public $type = 'CSS'; | |||
/** | |||
* Assoc array of attribute name to definition object. | |||
* @type HTMLPurifier_AttrDef[] | |||
*/ | |||
public $info = array(); | |||
/** | |||
* Constructs the info array. The meat of this class. | |||
* @param HTMLPurifier_Config $config | |||
*/ | |||
protected function doSetup($config) | |||
{ | |||
$this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( | |||
array('left', 'right', 'center', 'justify'), | |||
false | |||
); | |||
$border_style = | |||
$this->info['border-bottom-style'] = | |||
$this->info['border-right-style'] = | |||
$this->info['border-left-style'] = | |||
$this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( | |||
array( | |||
'none', | |||
'hidden', | |||
'dotted', | |||
'dashed', | |||
'solid', | |||
'double', | |||
'groove', | |||
'ridge', | |||
'inset', | |||
'outset' | |||
), | |||
false | |||
); | |||
$this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); | |||
$this->info['clear'] = new HTMLPurifier_AttrDef_Enum( | |||
array('none', 'left', 'right', 'both'), | |||
false | |||
); | |||
$this->info['float'] = new HTMLPurifier_AttrDef_Enum( | |||
array('none', 'left', 'right'), | |||
false | |||
); | |||
$this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( | |||
array('normal', 'italic', 'oblique'), | |||
false | |||
); | |||
$this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( | |||
array('normal', 'small-caps'), | |||
false | |||
); | |||
$uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum(array('none')), | |||
new HTMLPurifier_AttrDef_CSS_URI() | |||
) | |||
); | |||
$this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( | |||
array('inside', 'outside'), | |||
false | |||
); | |||
$this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( | |||
array( | |||
'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'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); | |||
$this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( | |||
array('capitalize', 'uppercase', 'lowercase', 'none'), | |||
false | |||
); | |||
$this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |||
$this->info['background-image'] = $uri_or_none; | |||
$this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( | |||
array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') | |||
); | |||
$this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( | |||
array('scroll', 'fixed') | |||
); | |||
$this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); | |||
$this->info['background-size'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum( | |||
array( | |||
'auto', | |||
'cover', | |||
'contain', | |||
'initial', | |||
'inherit', | |||
) | |||
), | |||
new HTMLPurifier_AttrDef_CSS_Percentage(), | |||
new HTMLPurifier_AttrDef_CSS_Length() | |||
) | |||
); | |||
$border_color = | |||
$this->info['border-top-color'] = | |||
$this->info['border-bottom-color'] = | |||
$this->info['border-left-color'] = | |||
$this->info['border-right-color'] = | |||
$this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum(array('transparent')), | |||
new HTMLPurifier_AttrDef_CSS_Color() | |||
) | |||
); | |||
$this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); | |||
$this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color); | |||
$border_width = | |||
$this->info['border-top-width'] = | |||
$this->info['border-bottom-width'] = | |||
$this->info['border-left-width'] = | |||
$this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), | |||
new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative | |||
) | |||
); | |||
$this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); | |||
$this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum(array('normal')), | |||
new HTMLPurifier_AttrDef_CSS_Length() | |||
) | |||
); | |||
$this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum(array('normal')), | |||
new HTMLPurifier_AttrDef_CSS_Length() | |||
) | |||
); | |||
$this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum( | |||
array( | |||
'xx-small', | |||
'x-small', | |||
'small', | |||
'medium', | |||
'large', | |||
'x-large', | |||
'xx-large', | |||
'larger', | |||
'smaller' | |||
) | |||
), | |||
new HTMLPurifier_AttrDef_CSS_Percentage(), | |||
new HTMLPurifier_AttrDef_CSS_Length() | |||
) | |||
); | |||
$this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum(array('normal')), | |||
new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives | |||
new HTMLPurifier_AttrDef_CSS_Length('0'), | |||
new HTMLPurifier_AttrDef_CSS_Percentage(true) | |||
) | |||
); | |||
$margin = | |||
$this->info['margin-top'] = | |||
$this->info['margin-bottom'] = | |||
$this->info['margin-left'] = | |||
$this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length(), | |||
new HTMLPurifier_AttrDef_CSS_Percentage(), | |||
new HTMLPurifier_AttrDef_Enum(array('auto')) | |||
) | |||
); | |||
$this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); | |||
// non-negative | |||
$padding = | |||
$this->info['padding-top'] = | |||
$this->info['padding-bottom'] = | |||
$this->info['padding-left'] = | |||
$this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length('0'), | |||
new HTMLPurifier_AttrDef_CSS_Percentage(true) | |||
) | |||
); | |||
$this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); | |||
$this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length(), | |||
new HTMLPurifier_AttrDef_CSS_Percentage() | |||
) | |||
); | |||
$trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length('0'), | |||
new HTMLPurifier_AttrDef_CSS_Percentage(true), | |||
new HTMLPurifier_AttrDef_Enum(array('auto', 'initial', 'inherit')) | |||
) | |||
); | |||
$trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length('0'), | |||
new HTMLPurifier_AttrDef_CSS_Percentage(true), | |||
new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')) | |||
) | |||
); | |||
$trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length('0'), | |||
new HTMLPurifier_AttrDef_CSS_Percentage(true), | |||
new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit')) | |||
) | |||
); | |||
$max = $config->get('CSS.MaxImgLength'); | |||
$this->info['width'] = | |||
$this->info['height'] = | |||
$max === null ? | |||
$trusted_wh : | |||
new HTMLPurifier_AttrDef_Switch( | |||
'img', | |||
// For img tags: | |||
new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length('0', $max), | |||
new HTMLPurifier_AttrDef_Enum(array('auto')) | |||
) | |||
), | |||
// For everyone else: | |||
$trusted_wh | |||
); | |||
$this->info['min-width'] = | |||
$this->info['min-height'] = | |||
$max === null ? | |||
$trusted_min_wh : | |||
new HTMLPurifier_AttrDef_Switch( | |||
'img', | |||
// For img tags: | |||
new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length('0', $max), | |||
new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit')) | |||
) | |||
), | |||
// For everyone else: | |||
$trusted_min_wh | |||
); | |||
$this->info['max-width'] = | |||
$this->info['max-height'] = | |||
$max === null ? | |||
$trusted_max_wh : | |||
new HTMLPurifier_AttrDef_Switch( | |||
'img', | |||
// For img tags: | |||
new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Length('0', $max), | |||
new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit')) | |||
) | |||
), | |||
// For everyone else: | |||
$trusted_max_wh | |||
); | |||
$this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); | |||
$this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); | |||
// this could use specialized code | |||
$this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( | |||
array( | |||
'normal', | |||
'bold', | |||
'bolder', | |||
'lighter', | |||
'100', | |||
'200', | |||
'300', | |||
'400', | |||
'500', | |||
'600', | |||
'700', | |||
'800', | |||
'900' | |||
), | |||
false | |||
); | |||
// MUST be called after other font properties, as it references | |||
// a CSSDefinition object | |||
$this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); | |||
// same here | |||
$this->info['border'] = | |||
$this->info['border-bottom'] = | |||
$this->info['border-top'] = | |||
$this->info['border-left'] = | |||
$this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); | |||
$this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum( | |||
array('collapse', 'separate') | |||
); | |||
$this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum( | |||
array('top', 'bottom') | |||
); | |||
$this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum( | |||
array('auto', 'fixed') | |||
); | |||
$this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_Enum( | |||
array( | |||
'baseline', | |||
'sub', | |||
'super', | |||
'top', | |||
'text-top', | |||
'middle', | |||
'bottom', | |||
'text-bottom' | |||
) | |||
), | |||
new HTMLPurifier_AttrDef_CSS_Length(), | |||
new HTMLPurifier_AttrDef_CSS_Percentage() | |||
) | |||
); | |||
$this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); | |||
// These CSS properties don't work on many browsers, but we live | |||
// in THE FUTURE! | |||
$this->info['white-space'] = new HTMLPurifier_AttrDef_Enum( | |||
array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line') | |||
); | |||
if ($config->get('CSS.Proprietary')) { | |||
$this->doSetupProprietary($config); | |||
} | |||
if ($config->get('CSS.AllowTricky')) { | |||
$this->doSetupTricky($config); | |||
} | |||
if ($config->get('CSS.Trusted')) { | |||
$this->doSetupTrusted($config); | |||
} | |||
$allow_important = $config->get('CSS.AllowImportant'); | |||
// wrap all attr-defs with decorator that handles !important | |||
foreach ($this->info as $k => $v) { | |||
$this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); | |||
} | |||
$this->setupConfigStuff($config); | |||
} | |||
/** | |||
* @param HTMLPurifier_Config $config | |||
*/ | |||
protected function doSetupProprietary($config) | |||
{ | |||
// Internet Explorer only scrollbar colors | |||
$this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |||
$this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |||
$this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |||
$this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |||
$this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |||
$this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); | |||
// vendor specific prefixes of opacity | |||
$this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); | |||
$this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); | |||
// only opacity, for now | |||
$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')); | |||
$border_radius = new HTMLPurifier_AttrDef_CSS_Composite( | |||
array( | |||
new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative | |||
new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative | |||
)); | |||
$this->info['border-top-left-radius'] = | |||
$this->info['border-top-right-radius'] = | |||
$this->info['border-bottom-right-radius'] = | |||
$this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2); | |||
// TODO: support SLASH syntax | |||
$this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4); | |||
} | |||
/** | |||
* @param HTMLPurifier_Config $config | |||
*/ | |||
protected function doSetupTricky($config) | |||
{ | |||
$this->info['display'] = new HTMLPurifier_AttrDef_Enum( | |||
array( | |||
'inline', | |||
'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['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); | |||
} | |||
/** | |||
* @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 | |||
* HTMLPurifier_HTMLDefinition. | |||
* @param HTMLPurifier_Config $config | |||
* @todo Refactor duplicate elements into common class (probably using | |||
* composition, not inheritance). | |||
*/ | |||
protected function setupConfigStuff($config) | |||
{ | |||
// setup allowed elements | |||
$support = "(for information on implementing this, see the " . | |||
"support forums) "; | |||
$allowed_properties = $config->get('CSS.AllowedProperties'); | |||
if ($allowed_properties !== null) { | |||
foreach ($this->info as $name => $d) { | |||
if (!isset($allowed_properties[$name])) { | |||
unset($this->info[$name]); | |||
} | |||
unset($allowed_properties[$name]); | |||
} | |||
// emit errors | |||
foreach ($allowed_properties as $name => $d) { | |||
// :TODO: Is this htmlspecialchars() call really necessary? | |||
$name = htmlspecialchars($name); | |||
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]); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,52 @@ | |||
<?php | |||
/** | |||
* Defines allowed child nodes and validates nodes against it. | |||
*/ | |||
abstract class HTMLPurifier_ChildDef | |||
{ | |||
/** | |||
* Type of child definition, usually right-most part of class name lowercase. | |||
* Used occasionally in terms of context. | |||
* @type string | |||
*/ | |||
public $type; | |||
/** | |||
* Indicates whether or not an empty array of children is okay. | |||
* | |||
* This is necessary for redundant checking when changes affecting | |||
* a child node may cause a parent node to now be disallowed. | |||
* @type bool | |||
*/ | |||
public $allow_empty; | |||
/** | |||
* Lookup array of all elements that this definition could possibly allow. | |||
* @type array | |||
*/ | |||
public $elements = array(); | |||
/** | |||
* Get lookup of tag names that should not close this element automatically. | |||
* All other elements will do so. | |||
* @param HTMLPurifier_Config $config HTMLPurifier_Config object | |||
* @return array | |||
*/ | |||
public function getAllowedElements($config) | |||
{ | |||
return $this->elements; | |||
} | |||
/** | |||
* Validates nodes according to definition and returns modification. | |||
* | |||
* @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node | |||
* @param HTMLPurifier_Config $config HTMLPurifier_Config object | |||
* @param HTMLPurifier_Context $context HTMLPurifier_Context object | |||
* @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children | |||
*/ | |||
abstract public function validateChildren($children, $config, $context); | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,67 @@ | |||
<?php | |||
/** | |||
* Definition that uses different definitions depending on context. | |||
* | |||
* The del and ins tags are notable because they allow different types of | |||
* elements depending on whether or not they're in a block or inline context. | |||
* Chameleon allows this behavior to happen by using two different | |||
* definitions depending on context. While this somewhat generalized, | |||
* it is specifically intended for those two tags. | |||
*/ | |||
class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef | |||
{ | |||
/** | |||
* Instance of the definition object to use when inline. Usually stricter. | |||
* @type HTMLPurifier_ChildDef_Optional | |||
*/ | |||
public $inline; | |||
/** | |||
* Instance of the definition object to use when block. | |||
* @type HTMLPurifier_ChildDef_Optional | |||
*/ | |||
public $block; | |||
/** | |||
* @type string | |||
*/ | |||
public $type = 'chameleon'; | |||
/** | |||
* @param array $inline List of elements to allow when inline. | |||
* @param array $block List of elements to allow when block. | |||
*/ | |||
public function __construct($inline, $block) | |||
{ | |||
$this->inline = new HTMLPurifier_ChildDef_Optional($inline); | |||
$this->block = new HTMLPurifier_ChildDef_Optional($block); | |||
$this->elements = $this->block->elements; | |||
} | |||
/** | |||
* @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) { | |||
return $this->block->validateChildren( | |||
$children, | |||
$config, | |||
$context | |||
); | |||
} else { | |||
return $this->inline->validateChildren( | |||
$children, | |||
$config, | |||
$context | |||
); | |||
} | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,102 @@ | |||
<?php | |||
/** | |||
* Custom validation class, accepts DTD child definitions | |||
* | |||
* @warning Currently this class is an all or nothing proposition, that is, | |||
* it will only give a bool return value. | |||
*/ | |||
class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef | |||
{ | |||
/** | |||
* @type string | |||
*/ | |||
public $type = 'custom'; | |||
/** | |||
* @type bool | |||
*/ | |||
public $allow_empty = false; | |||
/** | |||
* Allowed child pattern as defined by the DTD. | |||
* @type string | |||
*/ | |||
public $dtd_regex; | |||
/** | |||
* PCRE regex derived from $dtd_regex. | |||
* @type string | |||
*/ | |||
private $_pcre_regex; | |||
/** | |||
* @param $dtd_regex Allowed child pattern from the DTD | |||
*/ | |||
public function __construct($dtd_regex) | |||
{ | |||
$this->dtd_regex = $dtd_regex; | |||
$this->_compileRegex(); | |||
} | |||
/** | |||
* Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex) | |||
*/ | |||
protected function _compileRegex() | |||
{ | |||
$raw = str_replace(' ', '', $this->dtd_regex); | |||
if ($raw[0] != '(') { | |||
$raw = "($raw)"; | |||
} | |||
$el = '[#a-zA-Z0-9_.-]+'; | |||
$reg = $raw; | |||
// COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M | |||
// DOING! Seriously: if there's problems, please report them. | |||
// collect all elements into the $elements array | |||
preg_match_all("/$el/", $reg, $matches); | |||
foreach ($matches[0] as $match) { | |||
$this->elements[$match] = true; | |||
} | |||
// setup all elements as parentheticals with leading commas | |||
$reg = preg_replace("/$el/", '(,\\0)', $reg); | |||
// remove commas when they were not solicited | |||
$reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg); | |||
// remove all non-paranthetical commas: they are handled by first regex | |||
$reg = preg_replace("/,\(/", '(', $reg); | |||
$this->_pcre_regex = $reg; | |||
} | |||
/** | |||
* @param HTMLPurifier_Node[] $children | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return bool | |||
*/ | |||
public function validateChildren($children, $config, $context) | |||
{ | |||
$list_of_children = ''; | |||
$nesting = 0; // depth into the nest | |||
foreach ($children as $node) { | |||
if (!empty($node->is_whitespace)) { | |||
continue; | |||
} | |||
$list_of_children .= $node->name . ','; | |||
} | |||
// add leading comma to deal with stray comma declarations | |||
$list_of_children = ',' . rtrim($list_of_children, ','); | |||
$okay = | |||
preg_match( | |||
'/^,?' . $this->_pcre_regex . '$/', | |||
$list_of_children | |||
); | |||
return (bool)$okay; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,38 @@ | |||
<?php | |||
/** | |||
* Definition that disallows all elements. | |||
* @warning validateChildren() in this class is actually never called, because | |||
* empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed | |||
* before child definitions are parsed in earnest by | |||
* HTMLPurifier_Strategy_FixNesting. | |||
*/ | |||
class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef | |||
{ | |||
/** | |||
* @type bool | |||
*/ | |||
public $allow_empty = true; | |||
/** | |||
* @type string | |||
*/ | |||
public $type = 'empty'; | |||
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(); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,94 @@ | |||
<?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); | |||
public $whitespace; | |||
/** | |||
* @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; | |||
} | |||
// if li is not allowed, delete parent node | |||
if (!isset($config->getHTMLDefinition()->info['li'])) { | |||
trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING); | |||
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 = null; | |||
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 === null) { | |||
$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 |
@@ -0,0 +1,45 @@ | |||
<?php | |||
/** | |||
* Definition that allows a set of elements, and allows no children. | |||
* @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required, | |||
* really, one shouldn't inherit from the other. Only altered behavior | |||
* is to overload a returned false with an array. Thus, it will never | |||
* return false. | |||
*/ | |||
class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required | |||
{ | |||
/** | |||
* @type bool | |||
*/ | |||
public $allow_empty = true; | |||
/** | |||
* @type string | |||
*/ | |||
public $type = 'optional'; | |||
/** | |||
* @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 (empty($children)) { | |||
return true; | |||
} elseif ($this->whitespace) { | |||
return $children; | |||
} else { | |||
return array(); | |||
} | |||
} | |||
return $result; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,118 @@ | |||
<?php | |||
/** | |||
* Definition that allows a set of elements, but disallows empty children. | |||
*/ | |||
class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef | |||
{ | |||
/** | |||
* Lookup table of allowed elements. | |||
* @type array | |||
*/ | |||
public $elements = array(); | |||
/** | |||
* Whether or not the last passed node was all whitespace. | |||
* @type bool | |||
*/ | |||
protected $whitespace = false; | |||
/** | |||
* @param array|string $elements List of allowed element names (lowercase). | |||
*/ | |||
public function __construct($elements) | |||
{ | |||
if (is_string($elements)) { | |||
$elements = str_replace(' ', '', $elements); | |||
$elements = explode('|', $elements); | |||
} | |||
$keys = array_keys($elements); | |||
if ($keys == array_keys($keys)) { | |||
$elements = array_flip($elements); | |||
foreach ($elements as $i => $x) { | |||
$elements[$i] = true; | |||
if (empty($i)) { | |||
unset($elements[$i]); | |||
} // remove blank | |||
} | |||
} | |||
$this->elements = $elements; | |||
} | |||
/** | |||
* @type bool | |||
*/ | |||
public $allow_empty = false; | |||
/** | |||
* @type string | |||
*/ | |||
public $type = 'required'; | |||
/** | |||
* @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(); | |||
// whether or not parsed character data is allowed | |||
// this controls whether or not we silently drop a tag | |||
// or generate escaped HTML from it | |||
$pcdata_allowed = isset($this->elements['#PCDATA']); | |||
// a little sanity check to make sure it's not ALL whitespace | |||
$all_whitespace = true; | |||
$stack = array_reverse($children); | |||
while (!empty($stack)) { | |||
$node = array_pop($stack); | |||
if (!empty($node->is_whitespace)) { | |||
$result[] = $node; | |||
continue; | |||
} | |||
$all_whitespace = false; // phew, we're not talking about whitespace | |||
if (!isset($this->elements[$node->name])) { | |||
// special case text | |||
// XXX One of these ought to be redundant or something | |||
if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) { | |||
$result[] = $node; | |||
continue; | |||
} | |||
// spill the child contents in | |||
// ToDo: Make configurable | |||
if ($node instanceof HTMLPurifier_Node_Element) { | |||
for ($i = count($node->children) - 1; $i >= 0; $i--) { | |||
$stack[] = $node->children[$i]; | |||
} | |||
continue; | |||
} | |||
continue; | |||
} | |||
$result[] = $node; | |||
} | |||
if (empty($result)) { | |||
return false; | |||
} | |||
if ($all_whitespace) { | |||
$this->whitespace = true; | |||
return false; | |||
} | |||
return $result; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,110 @@ | |||
<?php | |||
/** | |||
* Takes the contents of blockquote when in strict and reformats for validation. | |||
*/ | |||
class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required | |||
{ | |||
/** | |||
* @type array | |||
*/ | |||
protected $real_elements; | |||
/** | |||
* @type array | |||
*/ | |||
protected $fake_elements; | |||
/** | |||
* @type bool | |||
*/ | |||
public $allow_empty = true; | |||
/** | |||
* @type string | |||
*/ | |||
public $type = 'strictblockquote'; | |||
/** | |||
* @type bool | |||
*/ | |||
protected $init = false; | |||
/** | |||
* @param HTMLPurifier_Config $config | |||
* @return array | |||
* @note We don't want MakeWellFormed to auto-close inline elements since | |||
* they might be allowed. | |||
*/ | |||
public function getAllowedElements($config) | |||
{ | |||
$this->init($config); | |||
return $this->fake_elements; | |||
} | |||
/** | |||
* @param array $children | |||
* @param HTMLPurifier_Config $config | |||
* @param HTMLPurifier_Context $context | |||
* @return array | |||
*/ | |||
public function validateChildren($children, $config, $context) | |||
{ | |||
$this->init($config); | |||
// trick the parent class into thinking it allows more | |||
$this->elements = $this->fake_elements; | |||
$result = parent::validateChildren($children, $config, $context); | |||
$this->elements = $this->real_elements; | |||
if ($result === false) { | |||
return array(); | |||
} | |||
if ($result === true) { | |||
$result = $children; | |||
} | |||
$def = $config->getHTMLDefinition(); | |||
$block_wrap_name = $def->info_block_wrapper; | |||
$block_wrap = false; | |||
$ret = array(); | |||
foreach ($result as $node) { | |||
if ($block_wrap === false) { | |||
if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) || | |||
($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) { | |||
$block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper); | |||
$ret[] = $block_wrap; | |||
} | |||
} else { | |||
if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) { | |||
$block_wrap = false; | |||
} | |||
} | |||
if ($block_wrap) { | |||
$block_wrap->children[] = $node; | |||
} else { | |||
$ret[] = $node; | |||
} | |||
} | |||
return $ret; | |||
} | |||
/** | |||
* @param HTMLPurifier_Config $config | |||
*/ | |||
private function init($config) | |||
{ | |||
if (!$this->init) { | |||
$def = $config->getHTMLDefinition(); | |||
// allow all inline elements | |||
$this->real_elements = $this->elements; | |||
$this->fake_elements = $def->info_content_sets['Flow']; | |||
$this->fake_elements['#PCDATA'] = true; | |||
$this->init = true; | |||
} | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,224 @@ | |||
<?php | |||
/** | |||
* 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 | |||
{ | |||
/** | |||
* @type bool | |||
*/ | |||
public $allow_empty = false; | |||
/** | |||
* @type string | |||
*/ | |||
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 | |||
$caption = false; | |||
$thead = 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 | |||
$cols = array(); | |||
$content = array(); | |||
$tbody_mode = false; // if true, then we need to wrap any stray | |||
// <tr>s with a <tbody>. | |||
$ws_accum =& $initial_ws; | |||
foreach ($children as $node) { | |||
if ($node instanceof HTMLPurifier_Node_Comment) { | |||
$ws_accum[] = $node; | |||
continue; | |||
} | |||
switch ($node->name) { | |||
case 'tbody': | |||
$tbody_mode = true; | |||
// fall through | |||
case 'tr': | |||
$content[] = $node; | |||
$ws_accum =& $content; | |||
break; | |||
case 'caption': | |||
// there can only be one caption! | |||
if ($caption !== false) break; | |||
$caption = $node; | |||
$ws_accum =& $after_caption_ws; | |||
break; | |||
case 'thead': | |||
$tbody_mode = true; | |||
// XXX This breaks rendering properties with | |||
// Firefox, which never floats a <thead> to | |||
// the top. Ever. (Our scheme will float the | |||
// 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 { | |||
// Oops, there's a second one! What | |||
// should we do? Current behavior is to | |||
// transmutate the first and last entries into | |||
// tbody tags, and then put into content. | |||
// 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; | |||
case 'colgroup': | |||
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; | |||
} | |||
} | |||
if (empty($content) && $thead === false && $tfoot === false) { | |||
return false; | |||
} | |||
$ret = $initial_ws; | |||
if ($caption !== false) { | |||
$ret[] = $caption; | |||
$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': | |||
$current_tr_tbody = null; | |||
$ret[] = $node; | |||
break; | |||
case 'tr': | |||
if ($current_tr_tbody === null) { | |||
$current_tr_tbody = new HTMLPurifier_Node_Element('tbody'); | |||
$ret[] = $current_tr_tbody; | |||
} | |||
$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); | |||
} | |||
return $ret; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,920 @@ | |||
<?php | |||
/** | |||
* Configuration object that triggers customizable behavior. | |||
* | |||
* @warning This class is strongly defined: that means that the class | |||
* will fail if an undefined directive is retrieved or set. | |||
* | |||
* @note Many classes that could (although many times don't) use the | |||
* configuration object make it a mandatory parameter. This is | |||
* because a configuration object should always be forwarded, | |||
* otherwise, you run the risk of missing a parameter and then | |||
* being stumped when a configuration directive doesn't work. | |||
* | |||
* @todo Reconsider some of the public member variables | |||
*/ | |||
class HTMLPurifier_Config | |||
{ | |||
/** | |||
* HTML Purifier's version | |||
* @type string | |||
*/ | |||
public $version = '4.15.0'; | |||
/** | |||
* Whether or not to automatically finalize | |||
* the object if a read operation is done. | |||
* @type bool | |||
*/ | |||
public $autoFinalize = true; | |||
// protected member variables | |||
/** | |||
* Namespace indexed array of serials for specific namespaces. | |||
* @see getSerial() for more info. | |||
* @type string[] | |||
*/ | |||
protected $serials = array(); | |||
/** | |||
* Serial for entire configuration object. | |||
* @type string | |||
*/ | |||
protected $serial; | |||
/** | |||
* Parser for variables. | |||
* @type HTMLPurifier_VarParser_Flexible | |||
*/ | |||
protected $parser = null; | |||
/** | |||
* Reference HTMLPurifier_ConfigSchema for value checking. | |||
* @type HTMLPurifier_ConfigSchema | |||
* @note This is public for introspective purposes. Please don't | |||
* abuse! | |||
*/ | |||
public $def; | |||
/** | |||
* Indexed array of definitions. | |||
* @type HTMLPurifier_Definition[] | |||
*/ | |||
protected $definitions; | |||
/** | |||
* Whether or not config is finalized. | |||
* @type bool | |||
*/ | |||
protected $finalized = false; | |||
/** | |||
* Property list containing configuration directives. | |||
* @type array | |||
*/ | |||
protected $plist; | |||
/** | |||
* Whether or not a set is taking place due to an alias lookup. | |||
* @type bool | |||
*/ | |||
private $aliasMode; | |||
/** | |||
* Set to false if you do not want line and file numbers in errors. | |||
* (useful when unit testing). This will also compress some errors | |||
* and exceptions. | |||
* @type bool | |||
*/ | |||
public $chatty = true; | |||
/** | |||
* Current lock; only gets to this namespace are allowed. | |||
* @type string | |||
*/ | |||
private $lock; | |||
/** | |||
* Constructor | |||
* @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines | |||
* what directives are allowed. | |||
* @param HTMLPurifier_PropertyList $parent | |||
*/ | |||
public function __construct($definition, $parent = null) | |||
{ | |||
$parent = $parent ? $parent : $definition->defaultPlist; | |||
$this->plist = new HTMLPurifier_PropertyList($parent); | |||
$this->def = $definition; // keep a copy around for checking | |||
$this->parser = new HTMLPurifier_VarParser_Flexible(); | |||
} | |||
/** | |||
* Convenience constructor that creates a config object based on a mixed var | |||
* @param mixed $config Variable that defines the state of the config | |||
* object. Can be: a HTMLPurifier_Config() object, | |||
* an array of directives based on loadArray(), | |||
* or a string filename of an ini file. | |||
* @param HTMLPurifier_ConfigSchema $schema Schema object | |||
* @return HTMLPurifier_Config Configured object | |||
*/ | |||
public static function create($config, $schema = null) | |||
{ | |||
if ($config instanceof HTMLPurifier_Config) { | |||
// pass-through | |||
return $config; | |||
} | |||
if (!$schema) { | |||
$ret = HTMLPurifier_Config::createDefault(); | |||
} else { | |||
$ret = new HTMLPurifier_Config($schema); | |||
} | |||
if (is_string($config)) { | |||
$ret->loadIni($config); | |||
} elseif (is_array($config)) $ret->loadArray($config); | |||
return $ret; | |||
} | |||
/** | |||
* Creates a new config object that inherits from a previous one. | |||
* @param HTMLPurifier_Config $config Configuration object to inherit from. | |||
* @return HTMLPurifier_Config object with $config as its parent. | |||
*/ | |||
public static function inherit(HTMLPurifier_Config $config) | |||
{ | |||
return new HTMLPurifier_Config($config->def, $config->plist); | |||
} | |||
/** | |||
* Convenience constructor that creates a default configuration object. | |||
* @return HTMLPurifier_Config default object. | |||
*/ | |||
public static function createDefault() | |||
{ | |||
$definition = HTMLPurifier_ConfigSchema::instance(); | |||
$config = new HTMLPurifier_Config($definition); | |||
return $config; | |||
} | |||
/** | |||
* Retrieves a value from the configuration. | |||
* | |||
* @param string $key String key | |||
* @param mixed $a | |||
* | |||
* @return mixed | |||
*/ | |||
public function get($key, $a = null) | |||
{ | |||
if ($a !== null) { | |||
$this->triggerError( | |||
"Using deprecated API: use \$config->get('$key.$a') instead", | |||
E_USER_WARNING | |||
); | |||
$key = "$key.$a"; | |||
} | |||
if (!$this->finalized) { | |||
$this->autoFinalize(); | |||
} | |||
if (!isset($this->def->info[$key])) { | |||
// can't add % due to SimpleTest bug | |||
$this->triggerError( | |||
'Cannot retrieve value of undefined directive ' . htmlspecialchars($key), | |||
E_USER_WARNING | |||
); | |||
return; | |||
} | |||
if (isset($this->def->info[$key]->isAlias)) { | |||
$d = $this->def->info[$key]; | |||
$this->triggerError( | |||
'Cannot get value from aliased directive, use real name ' . $d->key, | |||
E_USER_ERROR | |||
); | |||
return; | |||
} | |||
if ($this->lock) { | |||
list($ns) = explode('.', $key); | |||
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 | |||
); | |||
return; | |||
} | |||
} | |||
return $this->plist->get($key); | |||
} | |||
/** | |||
* Retrieves an array of directives to values from a given namespace | |||
* | |||
* @param string $namespace String namespace | |||
* | |||
* @return array | |||
*/ | |||
public function getBatch($namespace) | |||
{ | |||
if (!$this->finalized) { | |||
$this->autoFinalize(); | |||
} | |||
$full = $this->getAll(); | |||
if (!isset($full[$namespace])) { | |||
$this->triggerError( | |||
'Cannot retrieve undefined namespace ' . | |||
htmlspecialchars($namespace), | |||
E_USER_WARNING | |||
); | |||
return; | |||
} | |||
return $full[$namespace]; | |||
} | |||
/** | |||
* Returns a SHA-1 signature of a segment of the configuration object | |||
* 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 | |||
* before processing! | |||
*/ | |||
public function getBatchSerial($namespace) | |||
{ | |||
if (empty($this->serials[$namespace])) { | |||
$batch = $this->getBatch($namespace); | |||
unset($batch['DefinitionRev']); | |||
$this->serials[$namespace] = sha1(serialize($batch)); | |||
} | |||
return $this->serials[$namespace]; | |||
} | |||
/** | |||
* Returns a SHA-1 signature for the entire configuration object | |||
* that uniquely identifies that particular configuration | |||
* | |||
* @return string | |||
*/ | |||
public function getSerial() | |||
{ | |||
if (empty($this->serial)) { | |||
$this->serial = sha1(serialize($this->getAll())); | |||
} | |||
return $this->serial; | |||
} | |||
/** | |||
* Retrieves all directives, organized by namespace | |||
* | |||
* @warning This is a pretty inefficient function, avoid if you can | |||
*/ | |||
public function getAll() | |||
{ | |||
if (!$this->finalized) { | |||
$this->autoFinalize(); | |||
} | |||
$ret = array(); | |||
foreach ($this->plist->squash() as $name => $value) { | |||
list($ns, $key) = explode('.', $name, 2); | |||
$ret[$ns][$key] = $value; | |||
} | |||
return $ret; | |||
} | |||
/** | |||
* Sets a value to configuration. | |||
* | |||
* @param string $key key | |||
* @param mixed $value value | |||
* @param mixed $a | |||
*/ | |||
public function set($key, $value, $a = null) | |||
{ | |||
if (strpos($key, '.') === false) { | |||
$namespace = $key; | |||
$directive = $value; | |||
$value = $a; | |||
$key = "$key.$directive"; | |||
$this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); | |||
} else { | |||
list($namespace) = explode('.', $key); | |||
} | |||
if ($this->isFinalized('Cannot set directive after finalization')) { | |||
return; | |||
} | |||
if (!isset($this->def->info[$key])) { | |||
$this->triggerError( | |||
'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', | |||
E_USER_WARNING | |||
); | |||
return; | |||
} | |||
$def = $this->def->info[$key]; | |||
if (isset($def->isAlias)) { | |||
if ($this->aliasMode) { | |||
$this->triggerError( | |||
'Double-aliases not allowed, please fix '. | |||
'ConfigSchema bug with' . $key, | |||
E_USER_ERROR | |||
); | |||
return; | |||
} | |||
$this->aliasMode = true; | |||
$this->set($def->key, $value); | |||
$this->aliasMode = false; | |||
$this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); | |||
return; | |||
} | |||
// Raw type might be negative when using the fully optimized form | |||
// of stdClass, which indicates allow_null == true | |||
$rtype = is_int($def) ? $def : $def->type; | |||
if ($rtype < 0) { | |||
$type = -$rtype; | |||
$allow_null = true; | |||
} else { | |||
$type = $rtype; | |||
$allow_null = isset($def->allow_null); | |||
} | |||
try { | |||
$value = $this->parser->parse($value, $type, $allow_null); | |||
} catch (HTMLPurifier_VarParserException $e) { | |||
$this->triggerError( | |||
'Value for ' . $key . ' is of invalid type, should be ' . | |||
HTMLPurifier_VarParser::getTypeName($type), | |||
E_USER_WARNING | |||
); | |||
return; | |||
} | |||
if (is_string($value) && is_object($def)) { | |||
// resolve value alias if defined | |||
if (isset($def->aliases[$value])) { | |||
$value = $def->aliases[$value]; | |||
} | |||
// check to see if the value is allowed | |||
if (isset($def->allowed) && !isset($def->allowed[$value])) { | |||
$this->triggerError( | |||
'Value not supported, valid values are: ' . | |||
$this->_listify($def->allowed), | |||
E_USER_WARNING | |||
); | |||
return; | |||
} | |||
} | |||
$this->plist->set($key, $value); | |||
// reset definitions if the directives they depend on changed | |||
// this is a very costly process, so it's discouraged | |||
// with finalization | |||
if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { | |||
$this->definitions[$namespace] = null; | |||
} | |||
$this->serials[$namespace] = false; | |||
} | |||
/** | |||
* Convenience function for error reporting | |||
* | |||
* @param array $lookup | |||
* | |||
* @return string | |||
*/ | |||
private function _listify($lookup) | |||
{ | |||
$list = array(); | |||
foreach ($lookup as $name => $b) { | |||
$list[] = $name; | |||
} | |||
return implode(', ', $list); | |||
} | |||
/** | |||
* Retrieves object reference to the HTML 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 | |||
* maybeGetRawHTMLDefinition, which is more explicitly | |||
* named, instead. | |||
* | |||
* @return HTMLPurifier_HTMLDefinition|null | |||
*/ | |||
public function getHTMLDefinition($raw = false, $optimized = false) | |||
{ | |||
return $this->getDefinition('HTML', $raw, $optimized); | |||
} | |||
/** | |||
* Retrieves object reference to the CSS 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 | |||
* maybeGetRawCSSDefinition, which is more explicitly | |||
* named, instead. | |||
* | |||
* @return HTMLPurifier_CSSDefinition|null | |||
*/ | |||
public function getCSSDefinition($raw = false, $optimized = false) | |||
{ | |||
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|null | |||
*/ | |||
public function getURIDefinition($raw = false, $optimized = false) | |||
{ | |||
return $this->getDefinition('URI', $raw, $optimized); | |||
} | |||
/** | |||
* Retrieves a definition | |||
* | |||
* @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|null | |||
*/ | |||
public function getDefinition($type, $raw = false, $optimized = false) | |||
{ | |||
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 | |||
$lock = $this->lock; | |||
$this->lock = null; | |||
$factory = HTMLPurifier_DefinitionCacheFactory::instance(); | |||
$cache = $factory->create($type, $this); | |||
$this->lock = $lock; | |||
if (!$raw) { | |||
// full definition | |||
// --------------- | |||
// check if definition is in memory | |||
if (!empty($this->definitions[$type])) { | |||
$def = $this->definitions[$type]; | |||
// check if the definition is setup | |||
if ($def->setup) { | |||
return $def; | |||
} else { | |||
$def->setup($this); | |||
if ($def->optimized) { | |||
$cache->add($def, $this); | |||
} | |||
return $def; | |||
} | |||
} | |||
// 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 | |||
$this->lock = $type; | |||
$def->setup($this); | |||
$this->lock = null; | |||
// save in cache | |||
$cache->add($def, $this); | |||
// 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); | |||
} | |||
/** | |||
* @return HTMLPurifier_HTMLDefinition|null | |||
*/ | |||
public function maybeGetRawHTMLDefinition() | |||
{ | |||
return $this->getDefinition('HTML', true, true); | |||
} | |||
/** | |||
* @return HTMLPurifier_CSSDefinition|null | |||
*/ | |||
public function maybeGetRawCSSDefinition() | |||
{ | |||
return $this->getDefinition('CSS', true, true); | |||
} | |||
/** | |||
* @return HTMLPurifier_URIDefinition|null | |||
*/ | |||
public function maybeGetRawURIDefinition() | |||
{ | |||
return $this->getDefinition('URI', true, true); | |||
} | |||
/** | |||
* Loads configuration values from an array with the following structure: | |||
* Namespace.Directive => Value | |||
* | |||
* @param array $config_array Configuration associative array | |||
*/ | |||
public function loadArray($config_array) | |||
{ | |||
if ($this->isFinalized('Cannot load directives after finalization')) { | |||
return; | |||
} | |||
foreach ($config_array as $key => $value) { | |||
$key = str_replace('_', '.', $key); | |||
if (strpos($key, '.') !== false) { | |||
$this->set($key, $value); | |||
} else { | |||
$namespace = $key; | |||
$namespace_values = $value; | |||
foreach ($namespace_values as $directive => $value2) { | |||
$this->set($namespace .'.'. $directive, $value2); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Returns a list of array(namespace, directive) for all directives | |||
* that are allowed in a web-form context as per an allowed | |||
* namespaces/directives list. | |||
* | |||
* @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) | |||
{ | |||
if (!$schema) { | |||
$schema = HTMLPurifier_ConfigSchema::instance(); | |||
} | |||
if ($allowed !== true) { | |||
if (is_string($allowed)) { | |||
$allowed = array($allowed); | |||
} | |||
$allowed_ns = array(); | |||
$allowed_directives = array(); | |||
$blacklisted_directives = array(); | |||
foreach ($allowed as $ns_or_directive) { | |||
if (strpos($ns_or_directive, '.') !== false) { | |||
// directive | |||
if ($ns_or_directive[0] == '-') { | |||
$blacklisted_directives[substr($ns_or_directive, 1)] = true; | |||
} else { | |||
$allowed_directives[$ns_or_directive] = true; | |||
} | |||
} else { | |||
// namespace | |||
$allowed_ns[$ns_or_directive] = true; | |||
} | |||
} | |||
} | |||
$ret = array(); | |||
foreach ($schema->info as $key => $def) { | |||
list($ns, $directive) = explode('.', $key, 2); | |||
if ($allowed !== true) { | |||
if (isset($blacklisted_directives["$ns.$directive"])) { | |||
continue; | |||
} | |||
if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) { | |||
continue; | |||
} | |||
} | |||
if (isset($def->isAlias)) { | |||
continue; | |||
} | |||
if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') { | |||
continue; | |||
} | |||
$ret[] = array($ns, $directive); | |||
} | |||
return $ret; | |||
} | |||
/** | |||
* Loads configuration values from $_GET/$_POST that were posted | |||
* via ConfigForm | |||
* | |||
* @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 mixed | |||
*/ | |||
public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) | |||
{ | |||
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); | |||
$config = HTMLPurifier_Config::create($ret, $schema); | |||
return $config; | |||
} | |||
/** | |||
* Merges in configuration values from $_GET/$_POST to object. NOT STATIC. | |||
* | |||
* @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) | |||
{ | |||
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); | |||
$this->loadArray($ret); | |||
} | |||
/** | |||
* Prepares an array from a form into something usable for the more | |||
* 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) | |||
{ | |||
if ($index !== false) { | |||
$array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); | |||
} | |||
$mq = $mq_fix && version_compare(PHP_VERSION, '7.4.0', '<') && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); | |||
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); | |||
$ret = array(); | |||
foreach ($allowed as $key) { | |||
list($ns, $directive) = $key; | |||
$skey = "$ns.$directive"; | |||
if (!empty($array["Null_$skey"])) { | |||
$ret[$ns][$directive] = null; | |||
continue; | |||
} | |||
if (!isset($array[$skey])) { | |||
continue; | |||
} | |||
$value = $mq ? stripslashes($array[$skey]) : $array[$skey]; | |||
$ret[$ns][$directive] = $value; | |||
} | |||
return $ret; | |||
} | |||
/** | |||
* Loads configuration values from an ini file | |||
* | |||
* @param string $filename Name of ini file | |||
*/ | |||
public function loadIni($filename) | |||
{ | |||
if ($this->isFinalized('Cannot load directives after finalization')) { | |||
return; | |||
} | |||
$array = parse_ini_file($filename, true); | |||
$this->loadArray($array); | |||
} | |||
/** | |||
* Checks whether or not the configuration object is finalized. | |||
* | |||
* @param string|bool $error String error message, or false for no error | |||
* | |||
* @return bool | |||
*/ | |||
public function isFinalized($error = false) | |||
{ | |||
if ($this->finalized && $error) { | |||
$this->triggerError($error, E_USER_ERROR); | |||
} | |||
return $this->finalized; | |||
} | |||
/** | |||
* Finalizes configuration only if auto finalize is on and not | |||
* already finalized | |||
*/ | |||
public function autoFinalize() | |||
{ | |||
if ($this->autoFinalize) { | |||
$this->finalize(); | |||
} else { | |||
$this->plist->squash(true); | |||
} | |||
} | |||
/** | |||
* Finalizes a configuration object, prohibiting further change | |||
*/ | |||
public function finalize() | |||
{ | |||
$this->finalized = true; | |||
$this->parser = null; | |||
} | |||
/** | |||
* Produces a nicely formatted error message by supplying the | |||
* stack frame information OUTSIDE of HTMLPurifier_Config. | |||
* | |||
* @param string $msg An error message | |||
* @param int $no An error number | |||
*/ | |||
protected function triggerError($msg, $no) | |||
{ | |||
// determine previous stack frame | |||
$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 (isset($trace[$i + 1]['class']) && $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); | |||
} | |||
/** | |||
* Returns a serialized form of the configuration object that can | |||
* be reconstituted. | |||
* | |||
* @return string | |||
*/ | |||
public function serialize() | |||
{ | |||
$this->getDefinition('HTML'); | |||
$this->getDefinition('CSS'); | |||
$this->getDefinition('URI'); | |||
return serialize($this); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,176 @@ | |||
<?php | |||
/** | |||
* Configuration definition, defines directives and their defaults. | |||
*/ | |||
class HTMLPurifier_ConfigSchema | |||
{ | |||
/** | |||
* Defaults of the directives and namespaces. | |||
* @type array | |||
* @note This shares the exact same structure as HTMLPurifier_Config::$conf | |||
*/ | |||
public $defaults = array(); | |||
/** | |||
* The default property list. Do not edit this property list. | |||
* @type array | |||
*/ | |||
public $defaultPlist; | |||
/** | |||
* Definition of the directives. | |||
* The structure of this is: | |||
* | |||
* array( | |||
* 'Namespace' => array( | |||
* 'Directive' => new stdClass(), | |||
* ) | |||
* ) | |||
* | |||
* The stdClass may have the following properties: | |||
* | |||
* - If isAlias isn't set: | |||
* - type: Integer type of directive, see HTMLPurifier_VarParser for definitions | |||
* - allow_null: If set, this directive allows null values | |||
* - aliases: If set, an associative array of value aliases to real values | |||
* - allowed: If set, a lookup array of allowed (string) values | |||
* - If isAlias is set: | |||
* - namespace: Namespace this directive aliases to | |||
* - name: Directive name this directive aliases to | |||
* | |||
* In certain degenerate cases, stdClass will actually be an integer. In | |||
* that case, the value is equivalent to an stdClass with the type | |||
* property set to the integer. If the integer is negative, type is | |||
* equal to the absolute value of integer, and allow_null is true. | |||
* | |||
* This class is friendly with HTMLPurifier_Config. If you need introspection | |||
* about the schema, you're better of using the ConfigSchema_Interchange, | |||
* which uses more memory but has much richer information. | |||
* @type array | |||
*/ | |||
public $info = array(); | |||
/** | |||
* Application-wide singleton | |||
* @type HTMLPurifier_ConfigSchema | |||
*/ | |||
protected static $singleton; | |||
public function __construct() | |||
{ | |||
$this->defaultPlist = new HTMLPurifier_PropertyList(); | |||
} | |||
/** | |||
* Unserializes the default ConfigSchema. | |||
* @return HTMLPurifier_ConfigSchema | |||
*/ | |||
public static function makeFromSerial() | |||
{ | |||
$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. | |||
* @param HTMLPurifier_ConfigSchema $prototype | |||
* @return HTMLPurifier_ConfigSchema | |||
*/ | |||
public static function instance($prototype = null) | |||
{ | |||
if ($prototype !== null) { | |||
HTMLPurifier_ConfigSchema::$singleton = $prototype; | |||
} elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { | |||
HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial(); | |||
} | |||
return HTMLPurifier_ConfigSchema::$singleton; | |||
} | |||
/** | |||
* Defines a directive for configuration | |||
* @warning Will fail of directive's namespace is defined. | |||
* @warning This method's signature is slightly different from the legacy | |||
* define() static method! Beware! | |||
* @param string $key Name of directive | |||
* @param mixed $default Default value of directive | |||
* @param string $type Allowed type of the directive. See | |||
* HTMLPurifier_VarParser::$types for allowed values | |||
* @param bool $allow_null Whether or not to allow null values | |||
*/ | |||
public function add($key, $default, $type, $allow_null) | |||
{ | |||
$obj = new stdClass(); | |||
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type]; | |||
if ($allow_null) { | |||
$obj->allow_null = true; | |||
} | |||
$this->info[$key] = $obj; | |||
$this->defaults[$key] = $default; | |||
$this->defaultPlist->set($key, $default); | |||
} | |||
/** | |||
* Defines a directive value alias. | |||
* | |||
* Directive value aliases are convenient for developers because it lets | |||
* them set a directive to several values and get the same result. | |||
* @param string $key Name of Directive | |||
* @param array $aliases Hash of aliased values to the real alias | |||
*/ | |||
public function addValueAliases($key, $aliases) | |||
{ | |||
if (!isset($this->info[$key]->aliases)) { | |||
$this->info[$key]->aliases = array(); | |||
} | |||
foreach ($aliases as $alias => $real) { | |||
$this->info[$key]->aliases[$alias] = $real; | |||
} | |||
} | |||
/** | |||
* Defines a set of allowed values for a directive. | |||
* @warning This is slightly different from the corresponding static | |||
* method definition. | |||
* @param string $key Name of directive | |||
* @param array $allowed Lookup array of allowed values | |||
*/ | |||
public function addAllowedValues($key, $allowed) | |||
{ | |||
$this->info[$key]->allowed = $allowed; | |||
} | |||
/** | |||
* Defines a directive alias for backwards compatibility | |||
* @param string $key Directive that will be aliased | |||
* @param string $new_key Directive that the alias will be to | |||
*/ | |||
public function addAlias($key, $new_key) | |||
{ | |||
$obj = new stdClass; | |||
$obj->key = $new_key; | |||
$obj->isAlias = true; | |||
$this->info[$key] = $obj; | |||
} | |||
/** | |||
* Replaces any stdClass that only has the type property with type integer. | |||
*/ | |||
public function postProcess() | |||
{ | |||
foreach ($this->info as $key => $v) { | |||
if (count((array) $v) == 1) { | |||
$this->info[$key] = $v->type; | |||
} elseif (count((array) $v) == 2 && isset($v->allow_null)) { | |||
$this->info[$key] = -$v->type; | |||
} | |||
} | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,48 @@ | |||
<?php | |||
/** | |||
* Converts HTMLPurifier_ConfigSchema_Interchange to our runtime | |||
* representation used to perform checks on user configuration. | |||
*/ | |||
class HTMLPurifier_ConfigSchema_Builder_ConfigSchema | |||
{ | |||
/** | |||
* @param HTMLPurifier_ConfigSchema_Interchange $interchange | |||
* @return HTMLPurifier_ConfigSchema | |||
*/ | |||
public function build($interchange) | |||
{ | |||
$schema = new HTMLPurifier_ConfigSchema(); | |||
foreach ($interchange->directives as $d) { | |||
$schema->add( | |||
$d->id->key, | |||
$d->default, | |||
$d->type, | |||
$d->typeAllowsNull | |||
); | |||
if ($d->allowed !== null) { | |||
$schema->addAllowedValues( | |||
$d->id->key, | |||
$d->allowed | |||
); | |||
} | |||
foreach ($d->aliases as $alias) { | |||
$schema->addAlias( | |||
$alias->key, | |||
$d->id->key | |||
); | |||
} | |||
if ($d->valueAliases !== null) { | |||
$schema->addValueAliases( | |||
$d->id->key, | |||
$d->valueAliases | |||
); | |||
} | |||
} | |||
$schema->postProcess(); | |||
return $schema; | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,144 @@ | |||
<?php | |||
/** | |||
* Converts HTMLPurifier_ConfigSchema_Interchange to an XML format, | |||
* which can be further processed to generate documentation. | |||
*/ | |||
class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter | |||
{ | |||
/** | |||
* @type HTMLPurifier_ConfigSchema_Interchange | |||
*/ | |||
protected $interchange; | |||
/** | |||
* @type string | |||
*/ | |||
private $namespace; | |||
/** | |||
* @param string $html | |||
*/ | |||
protected function writeHTMLDiv($html) | |||
{ | |||
$this->startElement('div'); | |||
$purifier = HTMLPurifier::getInstance(); | |||
$html = $purifier->purify($html); | |||
$this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); | |||
$this->writeRaw($html); | |||
$this->endElement(); // div | |||
} | |||
/** | |||
* @param mixed $var | |||
* @return string | |||
*/ | |||
protected function export($var) | |||
{ | |||
if ($var === array()) { | |||
return 'array()'; | |||
} | |||
return var_export($var, true); | |||
} | |||
/** | |||
* @param HTMLPurifier_ConfigSchema_Interchange $interchange | |||
*/ | |||
public function build($interchange) | |||
{ | |||
// global access, only use as last resort | |||
$this->interchange = $interchange; | |||
$this->setIndent(true); | |||
$this->startDocument('1.0', 'UTF-8'); | |||
$this->startElement('configdoc'); | |||
$this->writeElement('title', $interchange->name); | |||
foreach ($interchange->directives as $directive) { | |||
$this->buildDirective($directive); | |||
} | |||
if ($this->namespace) { | |||
$this->endElement(); | |||
} // namespace | |||
$this->endElement(); // configdoc | |||
$this->flush(); | |||
} | |||
/** | |||
* @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive | |||
*/ | |||
public function buildDirective($directive) | |||
{ | |||
// Kludge, although I suppose having a notion of a "root namespace" | |||
// certainly makes things look nicer when documentation is built. | |||
// Depends on things being sorted. | |||
if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) { | |||
if ($this->namespace) { | |||
$this->endElement(); | |||
} // namespace | |||
$this->namespace = $directive->id->getRootNamespace(); | |||
$this->startElement('namespace'); | |||
$this->writeAttribute('id', $this->namespace); | |||
$this->writeElement('name', $this->namespace); | |||
} | |||
$this->startElement('directive'); | |||
$this->writeAttribute('id', $directive->id->toString()); | |||
$this->writeElement('name', $directive->id->getDirective()); | |||
$this->startElement('aliases'); | |||
foreach ($directive->aliases as $alias) { | |||
$this->writeElement('alias', $alias->toString()); | |||
} | |||
$this->endElement(); // aliases | |||
$this->startElement('constraints'); | |||
if ($directive->version) { | |||
$this->writeElement('version', $directive->version); | |||
} | |||
$this->startElement('type'); | |||
if ($directive->typeAllowsNull) { | |||
$this->writeAttribute('allow-null', 'yes'); | |||
} | |||
$this->text($directive->type); | |||
$this->endElement(); // type | |||
if ($directive->allowed) { | |||
$this->startElement('allowed'); | |||
foreach ($directive->allowed as $value => $x) { | |||
$this->writeElement('value', $value); | |||
} | |||
$this->endElement(); // allowed | |||
} | |||
$this->writeElement('default', $this->export($directive->default)); | |||
$this->writeAttribute('xml:space', 'preserve'); | |||
if ($directive->external) { | |||
$this->startElement('external'); | |||
foreach ($directive->external as $project) { | |||
$this->writeElement('project', $project); | |||
} | |||
$this->endElement(); | |||
} | |||
$this->endElement(); // constraints | |||
if ($directive->deprecatedVersion) { | |||
$this->startElement('deprecated'); | |||
$this->writeElement('version', $directive->deprecatedVersion); | |||
$this->writeElement('use', $directive->deprecatedUse->toString()); | |||
$this->endElement(); // deprecated | |||
} | |||
$this->startElement('description'); | |||
$this->writeHTMLDiv($directive->description); | |||
$this->endElement(); // description | |||
$this->endElement(); // directive | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,11 @@ | |||
<?php | |||
/** | |||
* Exceptions related to configuration schema | |||
*/ | |||
class HTMLPurifier_ConfigSchema_Exception extends HTMLPurifier_Exception | |||
{ | |||
} | |||
// vim: et sw=4 sts=4 |
@@ -0,0 +1,47 @@ | |||
<?php | |||
/** | |||
* Generic schema interchange format that can be converted to a runtime | |||
* representation (HTMLPurifier_ConfigSchema) or HTML documentation. Members | |||
* are completely validated. | |||
*/ | |||
class HTMLPurifier_ConfigSchema_Interchange | |||
{ | |||
/** | |||
* Name of the application this schema is describing. | |||
* @type string | |||
*/ | |||
public $name; | |||
/** | |||
* Array of Directive ID => array(directive info) | |||
* @type HTMLPurifier_ConfigSchema_Interchange_Directive[] | |||
*/ | |||
public $directives = array(); | |||
/** | |||
* Adds a directive array to $directives | |||
* @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive | |||
* @throws HTMLPurifier_ConfigSchema_Exception | |||
*/ | |||
public function addDirective($directive) | |||
{ | |||
if (isset($this->directives[$i = $directive->id->toString()])) { | |||
throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); | |||
} | |||
$this->directives[$i] = $directive; | |||
} | |||
/** | |||
* Convenience function to perform standard validation. Throws exception | |||
* on failed validation. | |||
*/ | |||
public function validate() | |||
{ | |||
$validator = new HTMLPurifier_ConfigSchema_Validator(); | |||
return $validator->validate($this); | |||
} | |||
} | |||
// vim: et sw=4 sts=4 |