Blame | Last modification | View Log | Download
<?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.12.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.*//*! @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.12.0 - Standards Compliant HTML FilteringCopyright (C) 2006-2008 Edward Z. YangThis library is free software; you can redistribute it and/ormodify it under the terms of the GNU Lesser General PublicLicense as published by the Free Software Foundation; eitherversion 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 ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNULesser General Public License for more details.You should have received a copy of the GNU Lesser General PublicLicense along with this library; if not, write to the Free SoftwareFoundation, 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.12.0';/*** Constant with version of HTML Purifier.*/const VERSION = '4.12.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 variablesif ($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 laterfor ($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();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);}}/*** 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(); // FIFOlist($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;}}/*** 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 modulesforeach ($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 inclusionsforeach ($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 inclusionsfor ($i = 0; isset($merge[$i]); $i++) {if (isset($seen[$merge[$i]])) {continue;}$seen[$merge[$i]] = true;// foreach attribute of the inclusion, copy it overif (!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 inclusionsif ($def_i === 0) {continue;}if (isset($processed[$def_i])) {continue;}// determine whether or not attribute is requiredif ($required = (strpos($def_i, '*') !== false)) {// rename the definitionunset($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 onif (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]);}}}}/*** 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;}}/*** 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;}}/*** 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();// 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 onif (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;}}/*** 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 thisforeach ($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 usedforeach ($attr as $attr_key => $value) {// call the definitionif (isset($defs[$attr_key])) {// there is a local definition definedif ($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 effectif ($result === false || $result === null) {// this is a generic error message that should replaced// with more specific ones when possibleif ($e) {$e->send(E_ERROR, 'AttrValidator: Attribute removed');}// remove the attributeunset($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 ourselvesif (!$current_token) {$context->destroy('CurrentToken');}}}// constants are slow, so we use as few as possibleif (!defined('HTMLPURIFIER_PREFIX')) {define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());}// 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 implementationsif (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 shenanigansspl_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 compatiblewith non-static object methods due to PHP Bug #44144;Please do not use HTMLPurifier.autoload.php (or anyfile 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'tif ($compat) {$func = implode('::', $func);}}spl_autoload_unregister($func);}spl_autoload_register($autoload);foreach ($funcs as $func) {spl_autoload_register($func);}}}}}/*** Super-class for definition datatype objects, implements serialization* functions for the class.*/abstract class HTMLPurifier_Definition{/*** Has setup() been called yet?* @type bool*/public $setup = false;/*** If true, write out the final definition object to the cache after* setup. This will be true only if all invocations to get a raw* definition object are also optimized. This does not cause file* system thrashing because on subsequent calls the cached object* is used and any writes to the raw definition object are short* circuited. See enduser-customize.html for the high-level* picture.* @type bool*/public $optimized = null;/*** What type of definition is it?* @type string*/public $type;/*** Sets up the definition object into the final form, something* not done by the constructor* @param HTMLPurifier_Config $config*/abstract protected function doSetup($config);/*** Setup function that aborts if already setup* @param HTMLPurifier_Config $config*/public function setup($config){if ($this->setup) {return;}$this->setup = true;$this->doSetup($config);}}/*** 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();$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 negativesnew 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 !importantforeach ($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 negativenew 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 errorsforeach ($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]);}}}}}/*** 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);}/*** 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.12.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-throughreturn $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 definedif (isset($def->aliases[$value])) {$value = $def->aliases[$value];}// check to see if the value is allowedif (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 finalizationif ($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*/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*/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*/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*/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 memoryif (!empty($this->definitions[$type])) {$def = $this->definitions[$type];// check if the definition is setupif ($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 itreturn $def;} else {// raw definition// --------------// check preconditions$def = null;if ($optimized) {if (is_null($this->get($type . '.DefinitionID'))) {// fatally error out if definition ID not setthrow 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 memoryif ($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 creationif (!$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 objectif ($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*/public function maybeGetRawHTMLDefinition(){return $this->getDefinition('HTML', true, true);}/*** @return HTMLPurifier_CSSDefinition*/public function maybeGetRawCSSDefinition(){return $this->getDefinition('CSS', true, true);}/*** @return HTMLPurifier_URIDefinition*/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) {// directiveif ($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 && 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 harfor ($i = 0, $c = count($trace); $i < $c - 1; $i++) {// XXX this is not correct on some versions of HTML Purifierif (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);}}/*** 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;}}}}/*** @todo Unit test*/class HTMLPurifier_ContentSets{/*** List of content set strings (pipe separators) indexed by name.* @type array*/public $info = array();/*** List of content set lookups (element => true) indexed by name.* @type array* @note This is in HTMLPurifier_HTMLDefinition->info_content_sets*/public $lookup = array();/*** Synchronized list of defined content sets (keys of info).* @type array*/protected $keys = array();/*** Synchronized list of defined content values (values of info).* @type array*/protected $values = array();/*** Merges in module's content sets, expands identifiers in the content* sets and populates the keys, values and lookup member variables.* @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule*/public function __construct($modules){if (!is_array($modules)) {$modules = array($modules);}// populate content_sets based on module hints// sorry, no way of overloadingforeach ($modules as $module) {foreach ($module->content_sets as $key => $value) {$temp = $this->convertToLookup($value);if (isset($this->lookup[$key])) {// add it into the existing content set$this->lookup[$key] = array_merge($this->lookup[$key], $temp);} else {$this->lookup[$key] = $temp;}}}$old_lookup = false;while ($old_lookup !== $this->lookup) {$old_lookup = $this->lookup;foreach ($this->lookup as $i => $set) {$add = array();foreach ($set as $element => $x) {if (isset($this->lookup[$element])) {$add += $this->lookup[$element];unset($this->lookup[$i][$element]);}}$this->lookup[$i] += $add;}}foreach ($this->lookup as $key => $lookup) {$this->info[$key] = implode(' | ', array_keys($lookup));}$this->keys = array_keys($this->info);$this->values = array_values($this->info);}/*** Accepts a definition; generates and assigns a ChildDef for it* @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference* @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef*/public function generateChildDef(&$def, $module){if (!empty($def->child)) { // already done!return;}$content_model = $def->content_model;if (is_string($content_model)) {// Assume that $this->keys is alphanumeric$def->content_model = preg_replace_callback('/\b(' . implode('|', $this->keys) . ')\b/',array($this, 'generateChildDefCallback'),$content_model);//$def->content_model = str_replace(// $this->keys, $this->values, $content_model);}$def->child = $this->getChildDef($def, $module);}public function generateChildDefCallback($matches){return $this->info[$matches[0]];}/*** Instantiates a ChildDef based on content_model and content_model_type* member variables in HTMLPurifier_ElementDef* @note This will also defer to modules for custom HTMLPurifier_ChildDef* subclasses that need content set expansion* @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted* @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef* @return HTMLPurifier_ChildDef corresponding to ElementDef*/public function getChildDef($def, $module){$value = $def->content_model;if (is_object($value)) {trigger_error('Literal object child definitions should be stored in '.'ElementDef->child not ElementDef->content_model',E_USER_NOTICE);return $value;}switch ($def->content_model_type) {case 'required':return new HTMLPurifier_ChildDef_Required($value);case 'optional':return new HTMLPurifier_ChildDef_Optional($value);case 'empty':return new HTMLPurifier_ChildDef_Empty();case 'custom':return new HTMLPurifier_ChildDef_Custom($value);}// defer to its module$return = false;if ($module->defines_child_def) { // save a func call$return = $module->getChildDef($def);}if ($return !== false) {return $return;}// error-outtrigger_error('Could not determine which ChildDef class to instantiate',E_USER_ERROR);return false;}/*** Converts a string list of elements separated by pipes into* a lookup array.* @param string $string List of elements* @return array Lookup array of elements*/protected function convertToLookup($string){$array = explode('|', str_replace(' ', '', $string));$ret = array();foreach ($array as $k) {$ret[$k] = true;}return $ret;}}/*** Registry object that contains information about the current context.* @warning Is a bit buggy when variables are set to null: it thinks* they don't exist! So use false instead, please.* @note Since the variables Context deals with may not be objects,* references are very important here! Do not remove!*/class HTMLPurifier_Context{/*** Private array that stores the references.* @type array*/private $_storage = array();/*** Registers a variable into the context.* @param string $name String name* @param mixed $ref Reference to variable to be registered*/public function register($name, &$ref){if (array_key_exists($name, $this->_storage)) {trigger_error("Name $name produces collision, cannot re-register",E_USER_ERROR);return;}$this->_storage[$name] =& $ref;}/*** Retrieves a variable reference from the context.* @param string $name String name* @param bool $ignore_error Boolean whether or not to ignore error* @return mixed*/public function &get($name, $ignore_error = false){if (!array_key_exists($name, $this->_storage)) {if (!$ignore_error) {trigger_error("Attempted to retrieve non-existent variable $name",E_USER_ERROR);}$var = null; // so we can return by referencereturn $var;}return $this->_storage[$name];}/*** Destroys a variable in the context.* @param string $name String name*/public function destroy($name){if (!array_key_exists($name, $this->_storage)) {trigger_error("Attempted to destroy non-existent variable $name",E_USER_ERROR);return;}unset($this->_storage[$name]);}/*** Checks whether or not the variable exists.* @param string $name String name* @return bool*/public function exists($name){return array_key_exists($name, $this->_storage);}/*** Loads a series of variables from an associative array* @param array $context_array Assoc array of variables to load*/public function loadArray($context_array){foreach ($context_array as $key => $discard) {$this->register($key, $context_array[$key]);}}}/*** Abstract class representing Definition cache managers that implements* useful common methods and is a factory.* @todo Create a separate maintenance file advanced users can use to* cache their custom HTMLDefinition, which can be loaded* via a configuration directive* @todo Implement memcached*/abstract class HTMLPurifier_DefinitionCache{/*** @type string*/public $type;/*** @param string $type Type of definition objects this instance of the* cache will handle.*/public function __construct($type){$this->type = $type;}/*** Generates a unique identifier for a particular configuration* @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config* @return string*/public function generateKey($config){return $config->version . ',' . // possibly replace with function calls$config->getBatchSerial($this->type) . ',' .$config->get($this->type . '.DefinitionRev');}/*** Tests whether or not a key is old with respect to the configuration's* version and revision number.* @param string $key Key to test* @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against* @return bool*/public function isOld($key, $config){if (substr_count($key, ',') < 2) {return true;}list($version, $hash, $revision) = explode(',', $key, 3);$compare = version_compare($version, $config->version);// version mismatch, is always oldif ($compare != 0) {return true;}// versions match, ids match, check revision numberif ($hash == $config->getBatchSerial($this->type) &&$revision < $config->get($this->type . '.DefinitionRev')) {return true;}return false;}/*** Checks if a definition's type jives with the cache's type* @note Throws an error on failure* @param HTMLPurifier_Definition $def Definition object to check* @return bool true if good, false if not*/public function checkDefType($def){if ($def->type !== $this->type) {trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");return false;}return true;}/*** Adds a definition object to the cache* @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config*/abstract public function add($def, $config);/*** Unconditionally saves a definition object to the cache* @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config*/abstract public function set($def, $config);/*** Replace an object in the cache* @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config*/abstract public function replace($def, $config);/*** Retrieves a definition object from the cache* @param HTMLPurifier_Config $config*/abstract public function get($config);/*** Removes a definition object to the cache* @param HTMLPurifier_Config $config*/abstract public function remove($config);/*** Clears all objects from cache* @param HTMLPurifier_Config $config*/abstract public function flush($config);/*** Clears all expired (older version or revision) objects from cache* @note Be careful implementing this method as flush. Flush must* not interfere with other Definition types, and cleanup()* should not be repeatedly called by userland code.* @param HTMLPurifier_Config $config*/abstract public function cleanup($config);}/*** Responsible for creating definition caches.*/class HTMLPurifier_DefinitionCacheFactory{/*** @type array*/protected $caches = array('Serializer' => array());/*** @type array*/protected $implementations = array();/*** @type HTMLPurifier_DefinitionCache_Decorator[]*/protected $decorators = array();/*** Initialize default decorators*/public function setup(){$this->addDecorator('Cleanup');}/*** Retrieves an instance of global definition cache factory.* @param HTMLPurifier_DefinitionCacheFactory $prototype* @return HTMLPurifier_DefinitionCacheFactory*/public static function instance($prototype = null){static $instance;if ($prototype !== null) {$instance = $prototype;} elseif ($instance === null || $prototype === true) {$instance = new HTMLPurifier_DefinitionCacheFactory();$instance->setup();}return $instance;}/*** Registers a new definition cache object* @param string $short Short name of cache object, for reference* @param string $long Full class name of cache object, for construction*/public function register($short, $long){$this->implementations[$short] = $long;}/*** Factory method that creates a cache object based on configuration* @param string $type Name of definitions handled by cache* @param HTMLPurifier_Config $config Config instance* @return mixed*/public function create($type, $config){$method = $config->get('Cache.DefinitionImpl');if ($method === null) {return new HTMLPurifier_DefinitionCache_Null($type);}if (!empty($this->caches[$method][$type])) {return $this->caches[$method][$type];}if (isset($this->implementations[$method]) &&class_exists($class = $this->implementations[$method], false)) {$cache = new $class($type);} else {if ($method != 'Serializer') {trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);}$cache = new HTMLPurifier_DefinitionCache_Serializer($type);}foreach ($this->decorators as $decorator) {$new_cache = $decorator->decorate($cache);// prevent infinite recursion in PHP 4unset($cache);$cache = $new_cache;}$this->caches[$method][$type] = $cache;return $this->caches[$method][$type];}/*** Registers a decorator to add to all new cache objects* @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator*/public function addDecorator($decorator){if (is_string($decorator)) {$class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";$decorator = new $class;}$this->decorators[$decorator->name] = $decorator;}}/*** Represents a document type, contains information on which modules* need to be loaded.* @note This class is inspected by Printer_HTMLDefinition->renderDoctype.* If structure changes, please update that function.*/class HTMLPurifier_Doctype{/*** Full name of doctype* @type string*/public $name;/*** List of standard modules (string identifiers or literal objects)* that this doctype uses* @type array*/public $modules = array();/*** List of modules to use for tidying up code* @type array*/public $tidyModules = array();/*** Is the language derived from XML (i.e. XHTML)?* @type bool*/public $xml = true;/*** List of aliases for this doctype* @type array*/public $aliases = array();/*** Public DTD identifier* @type string*/public $dtdPublic;/*** System DTD identifier* @type string*/public $dtdSystem;public function __construct($name = null,$xml = true,$modules = array(),$tidyModules = array(),$aliases = array(),$dtd_public = null,$dtd_system = null) {$this->name = $name;$this->xml = $xml;$this->modules = $modules;$this->tidyModules = $tidyModules;$this->aliases = $aliases;$this->dtdPublic = $dtd_public;$this->dtdSystem = $dtd_system;}}class HTMLPurifier_DoctypeRegistry{/*** Hash of doctype names to doctype objects.* @type array*/protected $doctypes;/*** Lookup table of aliases to real doctype names.* @type array*/protected $aliases;/*** Registers a doctype to the registry* @note Accepts a fully-formed doctype object, or the* parameters for constructing a doctype object* @param string $doctype Name of doctype or literal doctype object* @param bool $xml* @param array $modules Modules doctype will load* @param array $tidy_modules Modules doctype will load for certain modes* @param array $aliases Alias names for doctype* @param string $dtd_public* @param string $dtd_system* @return HTMLPurifier_Doctype Editable registered doctype*/public function register($doctype,$xml = true,$modules = array(),$tidy_modules = array(),$aliases = array(),$dtd_public = null,$dtd_system = null) {if (!is_array($modules)) {$modules = array($modules);}if (!is_array($tidy_modules)) {$tidy_modules = array($tidy_modules);}if (!is_array($aliases)) {$aliases = array($aliases);}if (!is_object($doctype)) {$doctype = new HTMLPurifier_Doctype($doctype,$xml,$modules,$tidy_modules,$aliases,$dtd_public,$dtd_system);}$this->doctypes[$doctype->name] = $doctype;$name = $doctype->name;// hookup aliasesforeach ($doctype->aliases as $alias) {if (isset($this->doctypes[$alias])) {continue;}$this->aliases[$alias] = $name;}// remove old aliasesif (isset($this->aliases[$name])) {unset($this->aliases[$name]);}return $doctype;}/*** Retrieves reference to a doctype of a certain name* @note This function resolves aliases* @note When possible, use the more fully-featured make()* @param string $doctype Name of doctype* @return HTMLPurifier_Doctype Editable doctype object*/public function get($doctype){if (isset($this->aliases[$doctype])) {$doctype = $this->aliases[$doctype];}if (!isset($this->doctypes[$doctype])) {trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);$anon = new HTMLPurifier_Doctype($doctype);return $anon;}return $this->doctypes[$doctype];}/*** Creates a doctype based on a configuration object,* will perform initialization on the doctype* @note Use this function to get a copy of doctype that config* can hold on to (this is necessary in order to tell* Generator whether or not the current document is XML* based or not).* @param HTMLPurifier_Config $config* @return HTMLPurifier_Doctype*/public function make($config){return clone $this->get($this->getDoctypeFromConfig($config));}/*** Retrieves the doctype from the configuration object* @param HTMLPurifier_Config $config* @return string*/public function getDoctypeFromConfig($config){// recommended test$doctype = $config->get('HTML.Doctype');if (!empty($doctype)) {return $doctype;}$doctype = $config->get('HTML.CustomDoctype');if (!empty($doctype)) {return $doctype;}// backwards-compatibilityif ($config->get('HTML.XHTML')) {$doctype = 'XHTML 1.0';} else {$doctype = 'HTML 4.01';}if ($config->get('HTML.Strict')) {$doctype .= ' Strict';} else {$doctype .= ' Transitional';}return $doctype;}}/*** Structure that stores an HTML element definition. Used by* HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.* @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.* Please update that class too.* @warning If you add new properties to this class, you MUST update* the mergeIn() method.*/class HTMLPurifier_ElementDef{/*** Does the definition work by itself, or is it created solely* for the purpose of merging into another definition?* @type bool*/public $standalone = true;/*** Associative array of attribute name to HTMLPurifier_AttrDef.* @type array* @note Before being processed by HTMLPurifier_AttrCollections* when modules are finalized during* HTMLPurifier_HTMLDefinition->setup(), this array may also* contain an array at index 0 that indicates which attribute* collections to load into the full array. It may also* contain string indentifiers in lieu of HTMLPurifier_AttrDef,* see HTMLPurifier_AttrTypes on how they are expanded during* HTMLPurifier_HTMLDefinition->setup() processing.*/public $attr = array();// XXX: Design note: currently, it's not possible to override// previously defined AttrTransforms without messing around with// the final generated config. This is by design; a previous version// used an associated list of attr_transform, but it was extremely// easy to accidentally override other attribute transforms by// forgetting to specify an index (and just using 0.) While we// could check this by checking the index number and complaining,// there is a second problem which is that it is not at all easy to// tell when something is getting overridden. Combine this with a// codebase where this isn't really being used, and it's perfect for// nuking./*** List of tags HTMLPurifier_AttrTransform to be done before validation.* @type array*/public $attr_transform_pre = array();/*** List of tags HTMLPurifier_AttrTransform to be done after validation.* @type array*/public $attr_transform_post = array();/*** HTMLPurifier_ChildDef of this tag.* @type HTMLPurifier_ChildDef*/public $child;/*** Abstract string representation of internal ChildDef rules.* @see HTMLPurifier_ContentSets for how this is parsed and then transformed* into an HTMLPurifier_ChildDef.* @warning This is a temporary variable that is not available after* being processed by HTMLDefinition* @type string*/public $content_model;/*** Value of $child->type, used to determine which ChildDef to use,* used in combination with $content_model.* @warning This must be lowercase* @warning This is a temporary variable that is not available after* being processed by HTMLDefinition* @type string*/public $content_model_type;/*** Does the element have a content model (#PCDATA | Inline)*? This* is important for chameleon ins and del processing in* HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't* have to worry about this one.* @type bool*/public $descendants_are_inline = false;/*** List of the names of required attributes this element has.* Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()* @type array*/public $required_attr = array();/*** Lookup table of tags excluded from all descendants of this tag.* @type array* @note SGML permits exclusions for all descendants, but this is* not possible with DTDs or XML Schemas. W3C has elected to* use complicated compositions of content_models to simulate* exclusion for children, but we go the simpler, SGML-style* route of flat-out exclusions, which correctly apply to* all descendants and not just children. Note that the XHTML* Modularization Abstract Modules are blithely unaware of such* distinctions.*/public $excludes = array();/*** This tag is explicitly auto-closed by the following tags.* @type array*/public $autoclose = array();/*** If a foreign element is found in this element, test if it is* allowed by this sub-element; if it is, instead of closing the* current element, place it inside this element.* @type string*/public $wrap;/*** Whether or not this is a formatting element affected by the* "Active Formatting Elements" algorithm.* @type bool*/public $formatting;/*** Low-level factory constructor for creating new standalone element defs*/public static function create($content_model, $content_model_type, $attr){$def = new HTMLPurifier_ElementDef();$def->content_model = $content_model;$def->content_model_type = $content_model_type;$def->attr = $attr;return $def;}/*** Merges the values of another element definition into this one.* Values from the new element def take precedence if a value is* not mergeable.* @param HTMLPurifier_ElementDef $def*/public function mergeIn($def){// later keys takes precedenceforeach ($def->attr as $k => $v) {if ($k === 0) {// merge in the includes// sorry, no way to override an includeforeach ($v as $v2) {$this->attr[0][] = $v2;}continue;}if ($v === false) {if (isset($this->attr[$k])) {unset($this->attr[$k]);}continue;}$this->attr[$k] = $v;}$this->_mergeAssocArray($this->excludes, $def->excludes);$this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);$this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);if (!empty($def->content_model)) {$this->content_model =str_replace("#SUPER", $this->content_model, $def->content_model);$this->child = false;}if (!empty($def->content_model_type)) {$this->content_model_type = $def->content_model_type;$this->child = false;}if (!is_null($def->child)) {$this->child = $def->child;}if (!is_null($def->formatting)) {$this->formatting = $def->formatting;}if ($def->descendants_are_inline) {$this->descendants_are_inline = $def->descendants_are_inline;}}/*** Merges one array into another, removes values which equal false* @param $a1 Array by reference that is merged into* @param $a2 Array that merges into $a1*/private function _mergeAssocArray(&$a1, $a2){foreach ($a2 as $k => $v) {if ($v === false) {if (isset($a1[$k])) {unset($a1[$k]);}continue;}$a1[$k] = $v;}}}/*** A UTF-8 specific character encoder that handles cleaning and transforming.* @note All functions in this class should be static.*/class HTMLPurifier_Encoder{/*** Constructor throws fatal error if you attempt to instantiate class*/private function __construct(){trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);}/*** Error-handler that mutes errors, alternative to shut-up operator.*/public static function muteErrorHandler(){}/*** iconv wrapper which mutes errors, but doesn't work around bugs.* @param string $in Input encoding* @param string $out Output encoding* @param string $text The text to convert* @return string*/public static function unsafeIconv($in, $out, $text){set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));$r = iconv($in, $out, $text);restore_error_handler();return $r;}/*** iconv wrapper which mutes errors and works around bugs.* @param string $in Input encoding* @param string $out Output encoding* @param string $text The text to convert* @param int $max_chunk_size* @return string*/public static function iconv($in, $out, $text, $max_chunk_size = 8000){$code = self::testIconvTruncateBug();if ($code == self::ICONV_OK) {return self::unsafeIconv($in, $out, $text);} elseif ($code == self::ICONV_TRUNCATES) {// we can only work around this if the input character set// is utf-8if ($in == 'utf-8') {if ($max_chunk_size < 4) {trigger_error('max_chunk_size is too small', E_USER_WARNING);return false;}// split into 8000 byte chunks, but be careful to handle// multibyte boundaries properlyif (($c = strlen($text)) <= $max_chunk_size) {return self::unsafeIconv($in, $out, $text);}$r = '';$i = 0;while (true) {if ($i + $max_chunk_size >= $c) {$r .= self::unsafeIconv($in, $out, substr($text, $i));break;}// wibble the boundaryif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {$chunk_size = $max_chunk_size;} elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {$chunk_size = $max_chunk_size - 1;} elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {$chunk_size = $max_chunk_size - 2;} elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {$chunk_size = $max_chunk_size - 3;} else {return false; // rather confusing UTF-8...}$chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths$r .= self::unsafeIconv($in, $out, $chunk);$i += $chunk_size;}return $r;} else {return false;}} else {return false;}}/*** Cleans a UTF-8 string for well-formedness and SGML validity** It will parse according to UTF-8 and return a valid UTF8 string, with* non-SGML codepoints excluded.** Specifically, it will permit:* \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}* Source: https://www.w3.org/TR/REC-xml/#NT-Char* Arguably this function should be modernized to the HTML5 set* of allowed characters:* https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream* which simultaneously expand and restrict the set of allowed characters.** @param string $str The string to clean* @param bool $force_php* @return string** @note Just for reference, the non-SGML code points are 0 to 31 and* 127 to 159, inclusive. However, we allow code points 9, 10* and 13, which are the tab, line feed and carriage return* respectively. 128 and above the code points map to multibyte* UTF-8 representations.** @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and* hsivonen@iki.fi at <http://iki.fi/hsivonen/php-utf8/> under the* LGPL license. Notes on what changed are inside, but in general,* the original code transformed UTF-8 text into an array of integer* Unicode codepoints. Understandably, transforming that back to* a string would be somewhat expensive, so the function was modded to* directly operate on the string. However, this discourages code* reuse, and the logic enumerated here would be useful for any* function that needs to be able to understand UTF-8 characters.* As of right now, only smart lossless character encoding converters* would need that, and I'm probably not going to implement them.*/public static function cleanUTF8($str, $force_php = false){// UTF-8 validity is checked since PHP 4.3.5// This is an optimization: if the string is already valid UTF-8, no// need to do PHP stuff. 99% of the time, this will be the case.if (preg_match('/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',$str)) {return $str;}$mState = 0; // cached expected number of octets after the current octet// until the beginning of the next UTF8 character sequence$mUcs4 = 0; // cached Unicode character$mBytes = 1; // cached expected number of octets in the current sequence// original code involved an $out that was an array of Unicode// codepoints. Instead of having to convert back into UTF-8, we've// decided to directly append valid UTF-8 characters onto a string// $out once they're done. $char accumulates raw bytes, while $mUcs4// turns into the Unicode code point, so there's some redundancy.$out = '';$char = '';$len = strlen($str);for ($i = 0; $i < $len; $i++) {$in = ord($str[$i]);$char .= $str[$i]; // append byte to charif (0 == $mState) {// When mState is zero we expect either a US-ASCII character// or a multi-octet sequence.if (0 == (0x80 & ($in))) {// US-ASCII, pass straight through.if (($in <= 31 || $in == 127) &&!($in == 9 || $in == 13 || $in == 10) // save \r\t\n) {// control characters, remove} else {$out .= $char;}// reset$char = '';$mBytes = 1;} elseif (0xC0 == (0xE0 & ($in))) {// First octet of 2 octet sequence$mUcs4 = ($in);$mUcs4 = ($mUcs4 & 0x1F) << 6;$mState = 1;$mBytes = 2;} elseif (0xE0 == (0xF0 & ($in))) {// First octet of 3 octet sequence$mUcs4 = ($in);$mUcs4 = ($mUcs4 & 0x0F) << 12;$mState = 2;$mBytes = 3;} elseif (0xF0 == (0xF8 & ($in))) {// First octet of 4 octet sequence$mUcs4 = ($in);$mUcs4 = ($mUcs4 & 0x07) << 18;$mState = 3;$mBytes = 4;} elseif (0xF8 == (0xFC & ($in))) {// First octet of 5 octet sequence.//// This is illegal because the encoded codepoint must be// either:// (a) not the shortest form or// (b) outside the Unicode range of 0-0x10FFFF.// Rather than trying to resynchronize, we will carry on// until the end of the sequence and let the later error// handling code catch it.$mUcs4 = ($in);$mUcs4 = ($mUcs4 & 0x03) << 24;$mState = 4;$mBytes = 5;} elseif (0xFC == (0xFE & ($in))) {// First octet of 6 octet sequence, see comments for 5// octet sequence.$mUcs4 = ($in);$mUcs4 = ($mUcs4 & 1) << 30;$mState = 5;$mBytes = 6;} else {// Current octet is neither in the US-ASCII range nor a// legal first octet of a multi-octet sequence.$mState = 0;$mUcs4 = 0;$mBytes = 1;$char = '';}} else {// When mState is non-zero, we expect a continuation of the// multi-octet sequenceif (0x80 == (0xC0 & ($in))) {// Legal continuation.$shift = ($mState - 1) * 6;$tmp = $in;$tmp = ($tmp & 0x0000003F) << $shift;$mUcs4 |= $tmp;if (0 == --$mState) {// End of the multi-octet sequence. mUcs4 now contains// the final Unicode codepoint to be output// Check for illegal sequences and codepoints.// From Unicode 3.1, non-shortest form is illegalif (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||((3 == $mBytes) && ($mUcs4 < 0x0800)) ||((4 == $mBytes) && ($mUcs4 < 0x10000)) ||(4 < $mBytes) ||// From Unicode 3.2, surrogate characters = illegal(($mUcs4 & 0xFFFFF800) == 0xD800) ||// Codepoints outside the Unicode range are illegal($mUcs4 > 0x10FFFF)) {} elseif (0xFEFF != $mUcs4 && // omit BOM// check for valid Char unicode codepoints(0x9 == $mUcs4 ||0xA == $mUcs4 ||0xD == $mUcs4 ||(0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||// 7F-9F is not strictly prohibited by XML,// but it is non-SGML, and thus we don't allow it(0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||(0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||(0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4))) {$out .= $char;}// initialize UTF8 cache (reset)$mState = 0;$mUcs4 = 0;$mBytes = 1;$char = '';}} else {// ((0xC0 & (*in) != 0x80) && (mState != 0))// Incomplete multi-octet sequence.// used to result in complete fail, but we'll reset$mState = 0;$mUcs4 = 0;$mBytes = 1;$char ='';}}}return $out;}/*** Translates a Unicode codepoint into its corresponding UTF-8 character.* @note Based on Feyd's function at* <http://forums.devnetwork.net/viewtopic.php?p=191404#191404>,* which is in public domain.* @note While we're going to do code point parsing anyway, a good* optimization would be to refuse to translate code points that* are non-SGML characters. However, this could lead to duplication.* @note This is very similar to the unichr function in* maintenance/generate-entity-file.php (although this is superior,* due to its sanity checks).*/// +----------+----------+----------+----------+// | 33222222 | 22221111 | 111111 | |// | 10987654 | 32109876 | 54321098 | 76543210 | bit// +----------+----------+----------+----------+// | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F// | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF// | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF// | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF// +----------+----------+----------+----------+// | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF)// | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes// +----------+----------+----------+----------+public static function unichr($code){if ($code > 1114111 or $code < 0 or($code >= 55296 and $code <= 57343) ) {// bits are set outside the "valid" range as defined// by UNICODE 4.1.0return '';}$x = $y = $z = $w = 0;if ($code < 128) {// regular ASCII character$x = $code;} else {// set up bits for UTF-8$x = ($code & 63) | 128;if ($code < 2048) {$y = (($code & 2047) >> 6) | 192;} else {$y = (($code & 4032) >> 6) | 128;if ($code < 65536) {$z = (($code >> 12) & 15) | 224;} else {$z = (($code >> 12) & 63) | 128;$w = (($code >> 18) & 7) | 240;}}}// set up the actual character$ret = '';if ($w) {$ret .= chr($w);}if ($z) {$ret .= chr($z);}if ($y) {$ret .= chr($y);}$ret .= chr($x);return $ret;}/*** @return bool*/public static function iconvAvailable(){static $iconv = null;if ($iconv === null) {$iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;}return $iconv;}/*** Convert a string to UTF-8 based on configuration.* @param string $str The string to convert* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return string*/public static function convertToUTF8($str, $config, $context){$encoding = $config->get('Core.Encoding');if ($encoding === 'utf-8') {return $str;}static $iconv = null;if ($iconv === null) {$iconv = self::iconvAvailable();}if ($iconv && !$config->get('Test.ForceNoIconv')) {// unaffected by bugs, since UTF-8 support all characters$str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);if ($str === false) {// $encoding is not a valid encodingtrigger_error('Invalid encoding ' . $encoding, E_USER_ERROR);return '';}// If the string is bjorked by Shift_JIS or a similar encoding// that doesn't support all of ASCII, convert the naughty// characters to their true byte-wise ASCII/UTF-8 equivalents.$str = strtr($str, self::testEncodingSupportsASCII($encoding));return $str;} elseif ($encoding === 'iso-8859-1') {$str = utf8_encode($str);return $str;}$bug = HTMLPurifier_Encoder::testIconvTruncateBug();if ($bug == self::ICONV_OK) {trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);} else {trigger_error('You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',E_USER_ERROR);}}/*** Converts a string from UTF-8 based on configuration.* @param string $str The string to convert* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return string* @note Currently, this is a lossy conversion, with unexpressable* characters being omitted.*/public static function convertFromUTF8($str, $config, $context){$encoding = $config->get('Core.Encoding');if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {$str = self::convertToASCIIDumbLossless($str);}if ($encoding === 'utf-8') {return $str;}static $iconv = null;if ($iconv === null) {$iconv = self::iconvAvailable();}if ($iconv && !$config->get('Test.ForceNoIconv')) {// Undo our previous fix in convertToUTF8, otherwise iconv will barf$ascii_fix = self::testEncodingSupportsASCII($encoding);if (!$escape && !empty($ascii_fix)) {$clear_fix = array();foreach ($ascii_fix as $utf8 => $native) {$clear_fix[$utf8] = '';}$str = strtr($str, $clear_fix);}$str = strtr($str, array_flip($ascii_fix));// Normal stuff$str = self::iconv('utf-8', $encoding . '//IGNORE', $str);return $str;} elseif ($encoding === 'iso-8859-1') {$str = utf8_decode($str);return $str;}trigger_error('Encoding not supported', E_USER_ERROR);// You might be tempted to assume that the ASCII representation// might be OK, however, this is *not* universally true over all// encodings. So we take the conservative route here, rather// than forcibly turn on %Core.EscapeNonASCIICharacters}/*** Lossless (character-wise) conversion of HTML to ASCII* @param string $str UTF-8 string to be converted to ASCII* @return string ASCII encoded string with non-ASCII character entity-ized* @warning Adapted from MediaWiki, claiming fair use: this is a common* algorithm. If you disagree with this license fudgery,* implement it yourself.* @note Uses decimal numeric entities since they are best supported.* @note This is a DUMB function: it has no concept of keeping* character entities that the projected character encoding* can allow. We could possibly implement a smart version* but that would require it to also know which Unicode* codepoints the charset supported (not an easy task).* @note Sort of with cleanUTF8() but it assumes that $str is* well-formed UTF-8*/public static function convertToASCIIDumbLossless($str){$bytesleft = 0;$result = '';$working = 0;$len = strlen($str);for ($i = 0; $i < $len; $i++) {$bytevalue = ord($str[$i]);if ($bytevalue <= 0x7F) { //0xxx xxxx$result .= chr($bytevalue);$bytesleft = 0;} elseif ($bytevalue <= 0xBF) { //10xx xxxx$working = $working << 6;$working += ($bytevalue & 0x3F);$bytesleft--;if ($bytesleft <= 0) {$result .= "&#" . $working . ";";}} elseif ($bytevalue <= 0xDF) { //110x xxxx$working = $bytevalue & 0x1F;$bytesleft = 1;} elseif ($bytevalue <= 0xEF) { //1110 xxxx$working = $bytevalue & 0x0F;$bytesleft = 2;} else { //1111 0xxx$working = $bytevalue & 0x07;$bytesleft = 3;}}return $result;}/** No bugs detected in iconv. */const ICONV_OK = 0;/** Iconv truncates output if converting from UTF-8 to another* character set with //IGNORE, and a non-encodable character is found */const ICONV_TRUNCATES = 1;/** Iconv does not support //IGNORE, making it unusable for* transcoding purposes */const ICONV_UNUSABLE = 2;/*** glibc iconv has a known bug where it doesn't handle the magic* //IGNORE stanza correctly. In particular, rather than ignore* characters, it will return an EILSEQ after consuming some number* of characters, and expect you to restart iconv as if it were* an E2BIG. Old versions of PHP did not respect the errno, and* returned the fragment, so as a result you would see iconv* mysteriously truncating output. We can work around this by* manually chopping our input into segments of about 8000* characters, as long as PHP ignores the error code. If PHP starts* paying attention to the error code, iconv becomes unusable.** @return int Error code indicating severity of bug.*/public static function testIconvTruncateBug(){static $code = null;if ($code === null) {// better not use iconv, otherwise infinite loop!$r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000));if ($r === false) {$code = self::ICONV_UNUSABLE;} elseif (($c = strlen($r)) < 9000) {$code = self::ICONV_TRUNCATES;} elseif ($c > 9000) {trigger_error('Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .'include your iconv version as per phpversion()',E_USER_ERROR);} else {$code = self::ICONV_OK;}}return $code;}/*** This expensive function tests whether or not a given character* encoding supports ASCII. 7/8-bit encodings like Shift_JIS will* fail this test, and require special processing. Variable width* encodings shouldn't ever fail.** @param string $encoding Encoding name to test, as per iconv format* @param bool $bypass Whether or not to bypass the precompiled arrays.* @return Array of UTF-8 characters to their corresponding ASCII,* which can be used to "undo" any overzealous iconv action.*/public static function testEncodingSupportsASCII($encoding, $bypass = false){// All calls to iconv here are unsafe, proof by case analysis:// If ICONV_OK, no difference.// If ICONV_TRUNCATE, all calls involve one character inputs,// so bug is not triggered.// If ICONV_UNUSABLE, this call is irrelevantstatic $encodings = array();if (!$bypass) {if (isset($encodings[$encoding])) {return $encodings[$encoding];}$lenc = strtolower($encoding);switch ($lenc) {case 'shift_jis':return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');case 'johab':return array("\xE2\x82\xA9" => '\\');}if (strpos($lenc, 'iso-8859-') === 0) {return array();}}$ret = array();if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) {return false;}for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars$c = chr($i); // UTF-8 char$r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversionif ($r === '' ||// This line is needed for iconv implementations that do not// omit characters that do not exist in the target character set($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)) {// Reverse engineer: what's the UTF-8 equiv of this byte// sequence? This assumes that there's no variable width// encoding that doesn't support ASCII.$ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c;}}$encodings[$encoding] = $ret;return $ret;}}/*** Object that provides entity lookup table from entity name to character*/class HTMLPurifier_EntityLookup{/*** Assoc array of entity name to character represented.* @type array*/public $table;/*** Sets up the entity lookup table from the serialized file contents.* @param bool $file* @note The serialized contents are versioned, but were generated* using the maintenance script generate_entity_file.php* @warning This is not in constructor to help enforce the Singleton*/public function setup($file = false){if (!$file) {$file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';}$this->table = unserialize(file_get_contents($file));}/*** Retrieves sole instance of the object.* @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with.* @return HTMLPurifier_EntityLookup*/public static function instance($prototype = false){// no references, since PHP doesn't copy unless modifiedstatic $instance = null;if ($prototype) {$instance = $prototype;} elseif (!$instance) {$instance = new HTMLPurifier_EntityLookup();$instance->setup();}return $instance;}}// if want to implement error collecting here, we'll need to use some sort// of global data (probably trigger_error) because it's impossible to pass// $config or $context to the callback functions./*** Handles referencing and derefencing character entities*/class HTMLPurifier_EntityParser{/*** Reference to entity lookup table.* @type HTMLPurifier_EntityLookup*/protected $_entity_lookup;/*** Callback regex string for entities in text.* @type string*/protected $_textEntitiesRegex;/*** Callback regex string for entities in attributes.* @type string*/protected $_attrEntitiesRegex;/*** Tests if the beginning of a string is a semi-optional regex*/protected $_semiOptionalPrefixRegex;public function __construct() {// From// http://stackoverflow.com/questions/15532252/why-is-reg-being-rendered-as-without-the-bounding-semicolon$semi_optional = "quot|QUOT|lt|LT|gt|GT|amp|AMP|AElig|Aacute|Acirc|Agrave|Aring|Atilde|Auml|COPY|Ccedil|ETH|Eacute|Ecirc|Egrave|Euml|Iacute|Icirc|Igrave|Iuml|Ntilde|Oacute|Ocirc|Ograve|Oslash|Otilde|Ouml|REG|THORN|Uacute|Ucirc|Ugrave|Uuml|Yacute|aacute|acirc|acute|aelig|agrave|aring|atilde|auml|brvbar|ccedil|cedil|cent|copy|curren|deg|divide|eacute|ecirc|egrave|eth|euml|frac12|frac14|frac34|iacute|icirc|iexcl|igrave|iquest|iuml|laquo|macr|micro|middot|nbsp|not|ntilde|oacute|ocirc|ograve|ordf|ordm|oslash|otilde|ouml|para|plusmn|pound|raquo|reg|sect|shy|sup1|sup2|sup3|szlig|thorn|times|uacute|ucirc|ugrave|uml|uuml|yacute|yen|yuml";// NB: three empty captures to put the fourth match in the right// place$this->_semiOptionalPrefixRegex = "/&()()()($semi_optional)/";$this->_textEntitiesRegex ='/&(?:'.// hex'[#]x([a-fA-F0-9]+);?|'.// dec'[#]0*(\d+);?|'.// string (mandatory semicolon)// NB: order matters: match semicolon preferentially'([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.// string (optional semicolon)"($semi_optional)".')/';$this->_attrEntitiesRegex ='/&(?:'.// hex'[#]x([a-fA-F0-9]+);?|'.// dec'[#]0*(\d+);?|'.// string (mandatory semicolon)// NB: order matters: match semicolon preferentially'([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.// string (optional semicolon)// don't match if trailing is equals or alphanumeric (URL// like)"($semi_optional)(?![=;A-Za-z0-9])".')/';}/*** Substitute entities with the parsed equivalents. Use this on* textual data in an HTML document (as opposed to attributes.)** @param string $string String to have entities parsed.* @return string Parsed string.*/public function substituteTextEntities($string){return preg_replace_callback($this->_textEntitiesRegex,array($this, 'entityCallback'),$string);}/*** Substitute entities with the parsed equivalents. Use this on* attribute contents in documents.** @param string $string String to have entities parsed.* @return string Parsed string.*/public function substituteAttrEntities($string){return preg_replace_callback($this->_attrEntitiesRegex,array($this, 'entityCallback'),$string);}/*** Callback function for substituteNonSpecialEntities() that does the work.** @param array $matches PCRE matches array, with 0 the entire match, and* either index 1, 2 or 3 set with a hex value, dec value,* or string (respectively).* @return string Replacement string.*/protected function entityCallback($matches){$entity = $matches[0];$hex_part = @$matches[1];$dec_part = @$matches[2];$named_part = empty($matches[3]) ? (empty($matches[4]) ? "" : $matches[4]) : $matches[3];if ($hex_part !== NULL && $hex_part !== "") {return HTMLPurifier_Encoder::unichr(hexdec($hex_part));} elseif ($dec_part !== NULL && $dec_part !== "") {return HTMLPurifier_Encoder::unichr((int) $dec_part);} else {if (!$this->_entity_lookup) {$this->_entity_lookup = HTMLPurifier_EntityLookup::instance();}if (isset($this->_entity_lookup->table[$named_part])) {return $this->_entity_lookup->table[$named_part];} else {// exact match didn't match anything, so test if// any of the semicolon optional match the prefix.// Test that this is an EXACT match is important to// prevent infinite loopif (!empty($matches[3])) {return preg_replace_callback($this->_semiOptionalPrefixRegex,array($this, 'entityCallback'),$entity);}return $entity;}}}// LEGACY CODE BELOW/*** Callback regex string for parsing entities.* @type string*/protected $_substituteEntitiesRegex ='/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';// 1. hex 2. dec 3. string (XML style)/*** Decimal to parsed string conversion table for special entities.* @type array*/protected $_special_dec2str =array(34 => '"',38 => '&',39 => "'",60 => '<',62 => '>');/*** Stripped entity names to decimal conversion table for special entities.* @type array*/protected $_special_ent2dec =array('quot' => 34,'amp' => 38,'lt' => 60,'gt' => 62);/*** Substitutes non-special entities with their parsed equivalents. Since* running this whenever you have parsed character is t3h 5uck, we run* it before everything else.** @param string $string String to have non-special entities parsed.* @return string Parsed string.*/public function substituteNonSpecialEntities($string){// it will try to detect missing semicolons, but don't rely on itreturn preg_replace_callback($this->_substituteEntitiesRegex,array($this, 'nonSpecialEntityCallback'),$string);}/*** Callback function for substituteNonSpecialEntities() that does the work.** @param array $matches PCRE matches array, with 0 the entire match, and* either index 1, 2 or 3 set with a hex value, dec value,* or string (respectively).* @return string Replacement string.*/protected function nonSpecialEntityCallback($matches){// replaces all but big five$entity = $matches[0];$is_num = (@$matches[0][1] === '#');if ($is_num) {$is_hex = (@$entity[2] === 'x');$code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];// abort for special charactersif (isset($this->_special_dec2str[$code])) {return $entity;}return HTMLPurifier_Encoder::unichr($code);} else {if (isset($this->_special_ent2dec[$matches[3]])) {return $entity;}if (!$this->_entity_lookup) {$this->_entity_lookup = HTMLPurifier_EntityLookup::instance();}if (isset($this->_entity_lookup->table[$matches[3]])) {return $this->_entity_lookup->table[$matches[3]];} else {return $entity;}}}/*** Substitutes only special entities with their parsed equivalents.** @notice We try to avoid calling this function because otherwise, it* would have to be called a lot (for every parsed section).** @param string $string String to have non-special entities parsed.* @return string Parsed string.*/public function substituteSpecialEntities($string){return preg_replace_callback($this->_substituteEntitiesRegex,array($this, 'specialEntityCallback'),$string);}/*** Callback function for substituteSpecialEntities() that does the work.** This callback has same syntax as nonSpecialEntityCallback().** @param array $matches PCRE-style matches array, with 0 the entire match, and* either index 1, 2 or 3 set with a hex value, dec value,* or string (respectively).* @return string Replacement string.*/protected function specialEntityCallback($matches){$entity = $matches[0];$is_num = (@$matches[0][1] === '#');if ($is_num) {$is_hex = (@$entity[2] === 'x');$int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];return isset($this->_special_dec2str[$int]) ?$this->_special_dec2str[$int] :$entity;} else {return isset($this->_special_ent2dec[$matches[3]]) ?$this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :$entity;}}}/*** Error collection class that enables HTML Purifier to report HTML* problems back to the user*/class HTMLPurifier_ErrorCollector{/*** Identifiers for the returned error array. These are purposely numeric* so list() can be used.*/const LINENO = 0;const SEVERITY = 1;const MESSAGE = 2;const CHILDREN = 3;/*** @type array*/protected $errors;/*** @type array*/protected $_current;/*** @type array*/protected $_stacks = array(array());/*** @type HTMLPurifier_Language*/protected $locale;/*** @type HTMLPurifier_Generator*/protected $generator;/*** @type HTMLPurifier_Context*/protected $context;/*** @type array*/protected $lines = array();/*** @param HTMLPurifier_Context $context*/public function __construct($context){$this->locale =& $context->get('Locale');$this->context = $context;$this->_current =& $this->_stacks[0];$this->errors =& $this->_stacks[0];}/*** Sends an error message to the collector for later use* @param int $severity Error severity, PHP error style (don't use E_USER_)* @param string $msg Error message text*/public function send($severity, $msg){$args = array();if (func_num_args() > 2) {$args = func_get_args();array_shift($args);unset($args[0]);}$token = $this->context->get('CurrentToken', true);$line = $token ? $token->line : $this->context->get('CurrentLine', true);$col = $token ? $token->col : $this->context->get('CurrentCol', true);$attr = $this->context->get('CurrentAttr', true);// perform special substitutions, also add custom parameters$subst = array();if (!is_null($token)) {$args['CurrentToken'] = $token;}if (!is_null($attr)) {$subst['$CurrentAttr.Name'] = $attr;if (isset($token->attr[$attr])) {$subst['$CurrentAttr.Value'] = $token->attr[$attr];}}if (empty($args)) {$msg = $this->locale->getMessage($msg);} else {$msg = $this->locale->formatMessage($msg, $args);}if (!empty($subst)) {$msg = strtr($msg, $subst);}// (numerically indexed)$error = array(self::LINENO => $line,self::SEVERITY => $severity,self::MESSAGE => $msg,self::CHILDREN => array());$this->_current[] = $error;// NEW CODE BELOW ...// Top-level errors are either:// TOKEN type, if $value is set appropriately, or// "syntax" type, if $value is null$new_struct = new HTMLPurifier_ErrorStruct();$new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;if ($token) {$new_struct->value = clone $token;}if (is_int($line) && is_int($col)) {if (isset($this->lines[$line][$col])) {$struct = $this->lines[$line][$col];} else {$struct = $this->lines[$line][$col] = $new_struct;}// These ksorts may present a performance problemksort($this->lines[$line], SORT_NUMERIC);} else {if (isset($this->lines[-1])) {$struct = $this->lines[-1];} else {$struct = $this->lines[-1] = $new_struct;}}ksort($this->lines, SORT_NUMERIC);// Now, check if we need to operate on a lower structureif (!empty($attr)) {$struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);if (!$struct->value) {$struct->value = array($attr, 'PUT VALUE HERE');}}if (!empty($cssprop)) {$struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);if (!$struct->value) {// if we tokenize CSS this might be a little more difficult to do$struct->value = array($cssprop, 'PUT VALUE HERE');}}// Ok, structs are all setup, now time to register the error$struct->addError($severity, $msg);}/*** Retrieves raw error data for custom formatter to use*/public function getRaw(){return $this->errors;}/*** Default HTML formatting implementation for error messages* @param HTMLPurifier_Config $config Configuration, vital for HTML output nature* @param array $errors Errors array to display; used for recursion.* @return string*/public function getHTMLFormatted($config, $errors = null){$ret = array();$this->generator = new HTMLPurifier_Generator($config, $this->context);if ($errors === null) {$errors = $this->errors;}// 'At line' message needs to be removed// generation code for new structure goes here. It needs to be recursive.foreach ($this->lines as $line => $col_array) {if ($line == -1) {continue;}foreach ($col_array as $col => $struct) {$this->_renderStruct($ret, $struct, $line, $col);}}if (isset($this->lines[-1])) {$this->_renderStruct($ret, $this->lines[-1]);}if (empty($errors)) {return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';} else {return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';}}private function _renderStruct(&$ret, $struct, $line = null, $col = null){$stack = array($struct);$context_stack = array(array());while ($current = array_pop($stack)) {$context = array_pop($context_stack);foreach ($current->errors as $error) {list($severity, $msg) = $error;$string = '';$string .= '<div>';// W3C uses an icon to indicate the severity of the error.$error = $this->locale->getErrorName($severity);$string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";if (!is_null($line) && !is_null($col)) {$string .= "<em class=\"location\">Line $line, Column $col: </em> ";} else {$string .= '<em class="location">End of Document: </em> ';}$string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';$string .= '</div>';// Here, have a marker for the character on the column appropriate.// Be sure to clip extremely long lines.//$string .= '<pre>';//$string .= '';//$string .= '</pre>';$ret[] = $string;}foreach ($current->children as $array) {$context[] = $current;$stack = array_merge($stack, array_reverse($array, true));for ($i = count($array); $i > 0; $i--) {$context_stack[] = $context;}}}}}/*** Records errors for particular segments of an HTML document such as tokens,* attributes or CSS properties. They can contain error structs (which apply* to components of what they represent), but their main purpose is to hold* errors applying to whatever struct is being used.*/class HTMLPurifier_ErrorStruct{/*** Possible values for $children first-key. Note that top-level structures* are automatically token-level.*/const TOKEN = 0;const ATTR = 1;const CSSPROP = 2;/*** Type of this struct.* @type string*/public $type;/*** Value of the struct we are recording errors for. There are various* values for this:* - TOKEN: Instance of HTMLPurifier_Token* - ATTR: array('attr-name', 'value')* - CSSPROP: array('prop-name', 'value')* @type mixed*/public $value;/*** Errors registered for this structure.* @type array*/public $errors = array();/*** Child ErrorStructs that are from this structure. For example, a TOKEN* ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional* array in structure: [TYPE]['identifier']* @type array*/public $children = array();/*** @param string $type* @param string $id* @return mixed*/public function getChild($type, $id){if (!isset($this->children[$type][$id])) {$this->children[$type][$id] = new HTMLPurifier_ErrorStruct();$this->children[$type][$id]->type = $type;}return $this->children[$type][$id];}/*** @param int $severity* @param string $message*/public function addError($severity, $message){$this->errors[] = array($severity, $message);}}/*** Global exception class for HTML Purifier; any exceptions we throw* are from here.*/class HTMLPurifier_Exception extends Exception{}/*** Represents a pre or post processing filter on HTML Purifier's output** Sometimes, a little ad-hoc fixing of HTML has to be done before* it gets sent through HTML Purifier: you can use filters to acheive* this effect. For instance, YouTube videos can be preserved using* this manner. You could have used a decorator for this task, but* PHP's support for them is not terribly robust, so we're going* to just loop through the filters.** Filters should be exited first in, last out. If there are three filters,* named 1, 2 and 3, the order of execution should go 1->preFilter,* 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,* 1->postFilter.** @note Methods are not declared abstract as it is perfectly legitimate* for an implementation not to want anything to happen on a step*/class HTMLPurifier_Filter{/*** Name of the filter for identification purposes.* @type string*/public $name;/*** Pre-processor function, handles HTML before HTML Purifier* @param string $html* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return string*/public function preFilter($html, $config, $context){return $html;}/*** Post-processor function, handles HTML after HTML Purifier* @param string $html* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return string*/public function postFilter($html, $config, $context){return $html;}}/*** Generates HTML from tokens.* @todo Refactor interface so that configuration/context is determined* upon instantiation, no need for messy generateFromTokens() calls* @todo Make some of the more internal functions protected, and have* unit tests work around that*/class HTMLPurifier_Generator{/*** Whether or not generator should produce XML output.* @type bool*/private $_xhtml = true;/*** :HACK: Whether or not generator should comment the insides of <script> tags.* @type bool*/private $_scriptFix = false;/*** Cache of HTMLDefinition during HTML output to determine whether or* not attributes should be minimized.* @type HTMLPurifier_HTMLDefinition*/private $_def;/*** Cache of %Output.SortAttr.* @type bool*/private $_sortAttr;/*** Cache of %Output.FlashCompat.* @type bool*/private $_flashCompat;/*** Cache of %Output.FixInnerHTML.* @type bool*/private $_innerHTMLFix;/*** Stack for keeping track of object information when outputting IE* compatibility code.* @type array*/private $_flashStack = array();/*** Configuration for the generator* @type HTMLPurifier_Config*/protected $config;/*** @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context*/public function __construct($config, $context){$this->config = $config;$this->_scriptFix = $config->get('Output.CommentScriptContents');$this->_innerHTMLFix = $config->get('Output.FixInnerHTML');$this->_sortAttr = $config->get('Output.SortAttr');$this->_flashCompat = $config->get('Output.FlashCompat');$this->_def = $config->getHTMLDefinition();$this->_xhtml = $this->_def->doctype->xml;}/*** Generates HTML from an array of tokens.* @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token* @return string Generated HTML*/public function generateFromTokens($tokens){if (!$tokens) {return '';}// Basic algorithm$html = '';for ($i = 0, $size = count($tokens); $i < $size; $i++) {if ($this->_scriptFix && $tokens[$i]->name === 'script'&& $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {// script special case// the contents of the script block must be ONE token// for this to work.$html .= $this->generateFromToken($tokens[$i++]);$html .= $this->generateScriptFromToken($tokens[$i++]);}$html .= $this->generateFromToken($tokens[$i]);}// Tidy cleanupif (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {$tidy = new Tidy;$tidy->parseString($html,array('indent'=> true,'output-xhtml' => $this->_xhtml,'show-body-only' => true,'indent-spaces' => 2,'wrap' => 68,),'utf8');$tidy->cleanRepair();$html = (string) $tidy; // explicit cast necessary}// Normalize newlines to system defined valueif ($this->config->get('Core.NormalizeNewlines')) {$nl = $this->config->get('Output.Newline');if ($nl === null) {$nl = PHP_EOL;}if ($nl !== "\n") {$html = str_replace("\n", $nl, $html);}}return $html;}/*** Generates HTML from a single token.* @param HTMLPurifier_Token $token HTMLPurifier_Token object.* @return string Generated HTML*/public function generateFromToken($token){if (!$token instanceof HTMLPurifier_Token) {trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);return '';} elseif ($token instanceof HTMLPurifier_Token_Start) {$attr = $this->generateAttributes($token->attr, $token->name);if ($this->_flashCompat) {if ($token->name == "object") {$flash = new stdClass();$flash->attr = $token->attr;$flash->param = array();$this->_flashStack[] = $flash;}}return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';} elseif ($token instanceof HTMLPurifier_Token_End) {$_extra = '';if ($this->_flashCompat) {if ($token->name == "object" && !empty($this->_flashStack)) {// doesn't do anything for now}}return $_extra . '</' . $token->name . '>';} elseif ($token instanceof HTMLPurifier_Token_Empty) {if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {$this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];}$attr = $this->generateAttributes($token->attr, $token->name);return '<' . $token->name . ($attr ? ' ' : '') . $attr .( $this->_xhtml ? ' /': '' ) // <br /> v. <br>. '>';} elseif ($token instanceof HTMLPurifier_Token_Text) {return $this->escape($token->data, ENT_NOQUOTES);} elseif ($token instanceof HTMLPurifier_Token_Comment) {return '<!--' . $token->data . '-->';} else {return '';}}/*** Special case processor for the contents of script tags* @param HTMLPurifier_Token $token HTMLPurifier_Token object.* @return string* @warning This runs into problems if there's already a literal* --> somewhere inside the script contents.*/public function generateScriptFromToken($token){if (!$token instanceof HTMLPurifier_Token_Text) {return $this->generateFromToken($token);}// Thanks <http://lachy.id.au/log/2005/05/script-comments>$data = preg_replace('#//\s*$#', '', $token->data);return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';}/*** Generates attribute declarations from attribute array.* @note This does not include the leading or trailing space.* @param array $assoc_array_of_attributes Attribute array* @param string $element Name of element attributes are for, used to check* attribute minimization.* @return string Generated HTML fragment for insertion.*/public function generateAttributes($assoc_array_of_attributes, $element = ''){$html = '';if ($this->_sortAttr) {ksort($assoc_array_of_attributes);}foreach ($assoc_array_of_attributes as $key => $value) {if (!$this->_xhtml) {// Remove namespaced attributesif (strpos($key, ':') !== false) {continue;}// Check if we should minimize the attribute: val="val" -> valif ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {$html .= $key . ' ';continue;}}// Workaround for Internet Explorer innerHTML bug.// Essentially, Internet Explorer, when calculating// innerHTML, omits quotes if there are no instances of// angled brackets, quotes or spaces. However, when parsing// HTML (for example, when you assign to innerHTML), it// treats backticks as quotes. Thus,// <img alt="``" />// becomes// <img alt=`` />// becomes// <img alt='' />// Fortunately, all we need to do is trigger an appropriate// quoting style, which we do by adding an extra space.// This also is consistent with the W3C spec, which states// that user agents may ignore leading or trailing// whitespace (in fact, most don't, at least for attributes// like alt, but an extra space at the end is barely// noticeable). Still, we have a configuration knob for// this, since this transformation is not necesary if you// don't process user input with innerHTML or you don't plan// on supporting Internet Explorer.if ($this->_innerHTMLFix) {if (strpos($value, '`') !== false) {// check if correct quoting style would not already be// triggeredif (strcspn($value, '"\' <>') === strlen($value)) {// protect!$value .= ' ';}}}$html .= $key.'="'.$this->escape($value).'" ';}return rtrim($html);}/*** Escapes raw text data.* @todo This really ought to be protected, but until we have a facility* for properly generating HTML here w/o using tokens, it stays* public.* @param string $string String data to escape for HTML.* @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is* permissible for non-attribute output.* @return string escaped data.*/public function escape($string, $quote = null){// Workaround for APC bug on Mac Leopard reported by sidepodcast// http://htmlpurifier.org/phorum/read.php?3,4823,4846if ($quote === null) {$quote = ENT_COMPAT;}return htmlspecialchars($string, $quote, 'UTF-8');}}/*** Definition of the purified HTML that describes allowed children,* attributes, and many other things.** Conventions:** All member variables that are prefixed with info* (including the main $info array) are used by HTML Purifier internals* and should not be directly edited when customizing the HTMLDefinition.* They can usually be set via configuration directives or custom* modules.** On the other hand, member variables without the info prefix are used* internally by the HTMLDefinition and MUST NOT be used by other HTML* Purifier internals. Many of them, however, are public, and may be* edited by userspace code to tweak the behavior of HTMLDefinition.** @note This class is inspected by Printer_HTMLDefinition; please* update that class if things here change.** @warning Directives that change this object's structure must be in* the HTML or Attr namespace!*/class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition{// FULLY-PUBLIC VARIABLES ---------------------------------------------/*** Associative array of element names to HTMLPurifier_ElementDef.* @type HTMLPurifier_ElementDef[]*/public $info = array();/*** Associative array of global attribute name to attribute definition.* @type array*/public $info_global_attr = array();/*** String name of parent element HTML will be going into.* @type string*/public $info_parent = 'div';/*** Definition for parent element, allows parent element to be a* tag that's not allowed inside the HTML fragment.* @type HTMLPurifier_ElementDef*/public $info_parent_def;/*** String name of element used to wrap inline elements in block context.* @type string* @note This is rarely used except for BLOCKQUOTEs in strict mode*/public $info_block_wrapper = 'p';/*** Associative array of deprecated tag name to HTMLPurifier_TagTransform.* @type array*/public $info_tag_transform = array();/*** Indexed list of HTMLPurifier_AttrTransform to be performed before validation.* @type HTMLPurifier_AttrTransform[]*/public $info_attr_transform_pre = array();/*** Indexed list of HTMLPurifier_AttrTransform to be performed after validation.* @type HTMLPurifier_AttrTransform[]*/public $info_attr_transform_post = array();/*** Nested lookup array of content set name (Block, Inline) to* element name to whether or not it belongs in that content set.* @type array*/public $info_content_sets = array();/*** Indexed list of HTMLPurifier_Injector to be used.* @type HTMLPurifier_Injector[]*/public $info_injector = array();/*** Doctype object* @type HTMLPurifier_Doctype*/public $doctype;// RAW CUSTOMIZATION STUFF --------------------------------------------/*** Adds a custom attribute to a pre-existing element* @note This is strictly convenience, and does not have a corresponding* method in HTMLPurifier_HTMLModule* @param string $element_name Element name to add attribute to* @param string $attr_name Name of attribute* @param mixed $def Attribute definition, can be string or object, see* HTMLPurifier_AttrTypes for details*/public function addAttribute($element_name, $attr_name, $def){$module = $this->getAnonymousModule();if (!isset($module->info[$element_name])) {$element = $module->addBlankElement($element_name);} else {$element = $module->info[$element_name];}$element->attr[$attr_name] = $def;}/*** Adds a custom element to your HTML definition* @see HTMLPurifier_HTMLModule::addElement() for detailed* parameter and return value descriptions.*/public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()){$module = $this->getAnonymousModule();// assume that if the user is calling this, the element// is safe. This may not be a good idea$element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);return $element;}/*** Adds a blank element to your HTML definition, for overriding* existing behavior* @param string $element_name* @return HTMLPurifier_ElementDef* @see HTMLPurifier_HTMLModule::addBlankElement() for detailed* parameter and return value descriptions.*/public function addBlankElement($element_name){$module = $this->getAnonymousModule();$element = $module->addBlankElement($element_name);return $element;}/*** Retrieves a reference to the anonymous module, so you can* bust out advanced features without having to make your own* module.* @return HTMLPurifier_HTMLModule*/public function getAnonymousModule(){if (!$this->_anonModule) {$this->_anonModule = new HTMLPurifier_HTMLModule();$this->_anonModule->name = 'Anonymous';}return $this->_anonModule;}private $_anonModule = null;// PUBLIC BUT INTERNAL VARIABLES --------------------------------------/*** @type string*/public $type = 'HTML';/*** @type HTMLPurifier_HTMLModuleManager*/public $manager;/*** Performs low-cost, preliminary initialization.*/public function __construct(){$this->manager = new HTMLPurifier_HTMLModuleManager();}/*** @param HTMLPurifier_Config $config*/protected function doSetup($config){$this->processModules($config);$this->setupConfigStuff($config);unset($this->manager);// cleanup some of the element definitionsforeach ($this->info as $k => $v) {unset($this->info[$k]->content_model);unset($this->info[$k]->content_model_type);}}/*** Extract out the information from the manager* @param HTMLPurifier_Config $config*/protected function processModules($config){if ($this->_anonModule) {// for user specific changes// this is late-loaded so we don't have to deal with PHP4// reference wonky-ness$this->manager->addModule($this->_anonModule);unset($this->_anonModule);}$this->manager->setup($config);$this->doctype = $this->manager->doctype;foreach ($this->manager->modules as $module) {foreach ($module->info_tag_transform as $k => $v) {if ($v === false) {unset($this->info_tag_transform[$k]);} else {$this->info_tag_transform[$k] = $v;}}foreach ($module->info_attr_transform_pre as $k => $v) {if ($v === false) {unset($this->info_attr_transform_pre[$k]);} else {$this->info_attr_transform_pre[$k] = $v;}}foreach ($module->info_attr_transform_post as $k => $v) {if ($v === false) {unset($this->info_attr_transform_post[$k]);} else {$this->info_attr_transform_post[$k] = $v;}}foreach ($module->info_injector as $k => $v) {if ($v === false) {unset($this->info_injector[$k]);} else {$this->info_injector[$k] = $v;}}}$this->info = $this->manager->getElements();$this->info_content_sets = $this->manager->contentSets->lookup;}/*** Sets up stuff based on config. We need a better way of doing this.* @param HTMLPurifier_Config $config*/protected function setupConfigStuff($config){$block_wrapper = $config->get('HTML.BlockWrapper');if (isset($this->info_content_sets['Block'][$block_wrapper])) {$this->info_block_wrapper = $block_wrapper;} else {trigger_error('Cannot use non-block element as block wrapper',E_USER_ERROR);}$parent = $config->get('HTML.Parent');$def = $this->manager->getElement($parent, true);if ($def) {$this->info_parent = $parent;$this->info_parent_def = $def;} else {trigger_error('Cannot use unrecognized element as parent',E_USER_ERROR);$this->info_parent_def = $this->manager->getElement($this->info_parent, true);}// support template text$support = "(for information on implementing this, see the support forums) ";// setup allowed elements -----------------------------------------$allowed_elements = $config->get('HTML.AllowedElements');$allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve earlyif (!is_array($allowed_elements) && !is_array($allowed_attributes)) {$allowed = $config->get('HTML.Allowed');if (is_string($allowed)) {list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);}}if (is_array($allowed_elements)) {foreach ($this->info as $name => $d) {if (!isset($allowed_elements[$name])) {unset($this->info[$name]);}unset($allowed_elements[$name]);}// emit errorsforeach ($allowed_elements as $element => $d) {$element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!trigger_error("Element '$element' is not supported $support", E_USER_WARNING);}}// setup allowed attributes ---------------------------------------$allowed_attributes_mutable = $allowed_attributes; // by copy!if (is_array($allowed_attributes)) {// This actually doesn't do anything, since we went away from// global attributes. It's possible that userland code uses// it, but HTMLModuleManager doesn't!foreach ($this->info_global_attr as $attr => $x) {$keys = array($attr, "*@$attr", "*.$attr");$delete = true;foreach ($keys as $key) {if ($delete && isset($allowed_attributes[$key])) {$delete = false;}if (isset($allowed_attributes_mutable[$key])) {unset($allowed_attributes_mutable[$key]);}}if ($delete) {unset($this->info_global_attr[$attr]);}}foreach ($this->info as $tag => $info) {foreach ($info->attr as $attr => $x) {$keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");$delete = true;foreach ($keys as $key) {if ($delete && isset($allowed_attributes[$key])) {$delete = false;}if (isset($allowed_attributes_mutable[$key])) {unset($allowed_attributes_mutable[$key]);}}if ($delete) {if ($this->info[$tag]->attr[$attr]->required) {trigger_error("Required attribute '$attr' in element '$tag' " ."was not allowed, which means '$tag' will not be allowed either",E_USER_WARNING);}unset($this->info[$tag]->attr[$attr]);}}}// emit errorsforeach ($allowed_attributes_mutable as $elattr => $d) {$bits = preg_split('/[.@]/', $elattr, 2);$c = count($bits);switch ($c) {case 2:if ($bits[0] !== '*') {$element = htmlspecialchars($bits[0]);$attribute = htmlspecialchars($bits[1]);if (!isset($this->info[$element])) {trigger_error("Cannot allow attribute '$attribute' if element " ."'$element' is not allowed/supported $support");} else {trigger_error("Attribute '$attribute' in element '$element' not supported $support",E_USER_WARNING);}break;}// otherwise fall throughcase 1:$attribute = htmlspecialchars($bits[0]);trigger_error("Global attribute '$attribute' is not "."supported in any elements $support",E_USER_WARNING);break;}}}// setup forbidden elements ---------------------------------------$forbidden_elements = $config->get('HTML.ForbiddenElements');$forbidden_attributes = $config->get('HTML.ForbiddenAttributes');foreach ($this->info as $tag => $info) {if (isset($forbidden_elements[$tag])) {unset($this->info[$tag]);continue;}foreach ($info->attr as $attr => $x) {if (isset($forbidden_attributes["$tag@$attr"]) ||isset($forbidden_attributes["*@$attr"]) ||isset($forbidden_attributes[$attr])) {unset($this->info[$tag]->attr[$attr]);continue;} elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually// $tag.$attr are not user supplied, so no worries!trigger_error("Error with $tag.$attr: tag.attr syntax not supported for " ."HTML.ForbiddenAttributes; use tag@attr instead",E_USER_WARNING);}}}foreach ($forbidden_attributes as $key => $v) {if (strlen($key) < 2) {continue;}if ($key[0] != '*') {continue;}if ($key[1] == '.') {trigger_error("Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",E_USER_WARNING);}}// setup injectors -----------------------------------------------------foreach ($this->info_injector as $i => $injector) {if ($injector->checkNeeded($config) !== false) {// remove injector that does not have it's required// elements/attributes present, and is thus not needed.unset($this->info_injector[$i]);}}}/*** Parses a TinyMCE-flavored Allowed Elements and Attributes list into* separate lists for processing. Format is element[attr1|attr2],element2...* @warning Although it's largely drawn from TinyMCE's implementation,* it is different, and you'll probably have to modify your lists* @param array $list String list to parse* @return array* @todo Give this its own class, probably static interface*/public function parseTinyMCEAllowedList($list){$list = str_replace(array(' ', "\t"), '', $list);$elements = array();$attributes = array();$chunks = preg_split('/(,|[\n\r]+)/', $list);foreach ($chunks as $chunk) {if (empty($chunk)) {continue;}// remove TinyMCE element control charactersif (!strpos($chunk, '[')) {$element = $chunk;$attr = false;} else {list($element, $attr) = explode('[', $chunk);}if ($element !== '*') {$elements[$element] = true;}if (!$attr) {continue;}$attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]$attr = explode('|', $attr);foreach ($attr as $key) {$attributes["$element.$key"] = true;}}return array($elements, $attributes);}}/*** Represents an XHTML 1.1 module, with information on elements, tags* and attributes.* @note Even though this is technically XHTML 1.1, it is also used for* regular HTML parsing. We are using modulization as a convenient* way to represent the internals of HTMLDefinition, and our* implementation is by no means conforming and does not directly* use the normative DTDs or XML schemas.* @note The public variables in a module should almost directly* correspond to the variables in HTMLPurifier_HTMLDefinition.* However, the prefix info carries no special meaning in these* objects (include it anyway if that's the correspondence though).* @todo Consider making some member functions protected*/class HTMLPurifier_HTMLModule{// -- Overloadable ----------------------------------------------------/*** Short unique string identifier of the module.* @type string*/public $name;/*** Informally, a list of elements this module changes.* Not used in any significant way.* @type array*/public $elements = array();/*** Associative array of element names to element definitions.* Some definitions may be incomplete, to be merged in later* with the full definition.* @type array*/public $info = array();/*** Associative array of content set names to content set additions.* This is commonly used to, say, add an A element to the Inline* content set. This corresponds to an internal variable $content_sets* and NOT info_content_sets member variable of HTMLDefinition.* @type array*/public $content_sets = array();/*** Associative array of attribute collection names to attribute* collection additions. More rarely used for adding attributes to* the global collections. Example is the StyleAttribute module adding* the style attribute to the Core. Corresponds to HTMLDefinition's* attr_collections->info, since the object's data is only info,* with extra behavior associated with it.* @type array*/public $attr_collections = array();/*** Associative array of deprecated tag name to HTMLPurifier_TagTransform.* @type array*/public $info_tag_transform = array();/*** List of HTMLPurifier_AttrTransform to be performed before validation.* @type array*/public $info_attr_transform_pre = array();/*** List of HTMLPurifier_AttrTransform to be performed after validation.* @type array*/public $info_attr_transform_post = array();/*** List of HTMLPurifier_Injector to be performed during well-formedness fixing.* An injector will only be invoked if all of it's pre-requisites are met;* if an injector fails setup, there will be no error; it will simply be* silently disabled.* @type array*/public $info_injector = array();/*** Boolean flag that indicates whether or not getChildDef is implemented.* For optimization reasons: may save a call to a function. Be sure* to set it if you do implement getChildDef(), otherwise it will have* no effect!* @type bool*/public $defines_child_def = false;/*** Boolean flag whether or not this module is safe. If it is not safe, all* of its members are unsafe. Modules are safe by default (this might be* slightly dangerous, but it doesn't make much sense to force HTML Purifier,* which is based off of safe HTML, to explicitly say, "This is safe," even* though there are modules which are "unsafe")** @type bool* @note Previously, safety could be applied at an element level granularity.* We've removed this ability, so in order to add "unsafe" elements* or attributes, a dedicated module with this property set to false* must be used.*/public $safe = true;/*** Retrieves a proper HTMLPurifier_ChildDef subclass based on* content_model and content_model_type member variables of* the HTMLPurifier_ElementDef class. There is a similar function* in HTMLPurifier_HTMLDefinition.* @param HTMLPurifier_ElementDef $def* @return HTMLPurifier_ChildDef subclass*/public function getChildDef($def){return false;}// -- Convenience -----------------------------------------------------/*** Convenience function that sets up a new element* @param string $element Name of element to add* @param string|bool $type What content set should element be registered to?* Set as false to skip this step.* @param string|HTMLPurifier_ChildDef $contents Allowed children in form of:* "$content_model_type: $content_model"* @param array|string $attr_includes What attribute collections to register to* element?* @param array $attr What unique attributes does the element define?* @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters.* @return HTMLPurifier_ElementDef Created element definition object, so you* can set advanced parameters*/public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()){$this->elements[] = $element;// parse content_modellist($content_model_type, $content_model) = $this->parseContents($contents);// merge in attribute inclusions$this->mergeInAttrIncludes($attr, $attr_includes);// add element to content setsif ($type) {$this->addElementToContentSet($element, $type);}// create element$this->info[$element] = HTMLPurifier_ElementDef::create($content_model,$content_model_type,$attr);// literal object $contents means direct child manipulationif (!is_string($contents)) {$this->info[$element]->child = $contents;}return $this->info[$element];}/*** Convenience function that creates a totally blank, non-standalone* element.* @param string $element Name of element to create* @return HTMLPurifier_ElementDef Created element*/public function addBlankElement($element){if (!isset($this->info[$element])) {$this->elements[] = $element;$this->info[$element] = new HTMLPurifier_ElementDef();$this->info[$element]->standalone = false;} else {trigger_error("Definition for $element already exists in module, cannot redefine");}return $this->info[$element];}/*** Convenience function that registers an element to a content set* @param string $element Element to register* @param string $type Name content set (warning: case sensitive, usually upper-case* first letter)*/public function addElementToContentSet($element, $type){if (!isset($this->content_sets[$type])) {$this->content_sets[$type] = '';} else {$this->content_sets[$type] .= ' | ';}$this->content_sets[$type] .= $element;}/*** Convenience function that transforms single-string contents* into separate content model and content model type* @param string $contents Allowed children in form of:* "$content_model_type: $content_model"* @return array* @note If contents is an object, an array of two nulls will be* returned, and the callee needs to take the original $contents* and use it directly.*/public function parseContents($contents){if (!is_string($contents)) {return array(null, null);} // deferswitch ($contents) {// check for shorthand content model formscase 'Empty':return array('empty', '');case 'Inline':return array('optional', 'Inline | #PCDATA');case 'Flow':return array('optional', 'Flow | #PCDATA');}list($content_model_type, $content_model) = explode(':', $contents);$content_model_type = strtolower(trim($content_model_type));$content_model = trim($content_model);return array($content_model_type, $content_model);}/*** Convenience function that merges a list of attribute includes into* an attribute array.* @param array $attr Reference to attr array to modify* @param array $attr_includes Array of includes / string include to merge in*/public function mergeInAttrIncludes(&$attr, $attr_includes){if (!is_array($attr_includes)) {if (empty($attr_includes)) {$attr_includes = array();} else {$attr_includes = array($attr_includes);}}$attr[0] = $attr_includes;}/*** Convenience function that generates a lookup table with boolean* true as value.* @param string $list List of values to turn into a lookup* @note You can also pass an arbitrary number of arguments in* place of the regular argument* @return array array equivalent of list*/public function makeLookup($list){if (is_string($list)) {$list = func_get_args();}$ret = array();foreach ($list as $value) {if (is_null($value)) {continue;}$ret[$value] = true;}return $ret;}/*** Lazy load construction of the module after determining whether* or not it's needed, and also when a finalized configuration object* is available.* @param HTMLPurifier_Config $config*/public function setup($config){}}class HTMLPurifier_HTMLModuleManager{/*** @type HTMLPurifier_DoctypeRegistry*/public $doctypes;/*** Instance of current doctype.* @type string*/public $doctype;/*** @type HTMLPurifier_AttrTypes*/public $attrTypes;/*** Active instances of modules for the specified doctype are* indexed, by name, in this array.* @type HTMLPurifier_HTMLModule[]*/public $modules = array();/*** Array of recognized HTMLPurifier_HTMLModule instances,* indexed by module's class name. This array is usually lazy loaded, but a* user can overload a module by pre-emptively registering it.* @type HTMLPurifier_HTMLModule[]*/public $registeredModules = array();/*** List of extra modules that were added by the user* using addModule(). These get unconditionally merged into the current doctype, whatever* it may be.* @type HTMLPurifier_HTMLModule[]*/public $userModules = array();/*** Associative array of element name to list of modules that have* definitions for the element; this array is dynamically filled.* @type array*/public $elementLookup = array();/*** List of prefixes we should use for registering small names.* @type array*/public $prefixes = array('HTMLPurifier_HTMLModule_');/*** @type HTMLPurifier_ContentSets*/public $contentSets;/*** @type HTMLPurifier_AttrCollections*/public $attrCollections;/*** If set to true, unsafe elements and attributes will be allowed.* @type bool*/public $trusted = false;public function __construct(){// editable internal objects$this->attrTypes = new HTMLPurifier_AttrTypes();$this->doctypes = new HTMLPurifier_DoctypeRegistry();// setup basic modules$common = array('CommonAttributes', 'Text', 'Hypertext', 'List','Presentation', 'Edit', 'Bdo', 'Tables', 'Image','StyleAttribute',// Unsafe:'Scripting', 'Object', 'Forms',// Sorta legacy, but present in strict:'Name',);$transitional = array('Legacy', 'Target', 'Iframe');$xml = array('XMLCommonAttributes');$non_xml = array('NonXMLCommonAttributes');// setup basic doctypes$this->doctypes->register('HTML 4.01 Transitional',false,array_merge($common, $transitional, $non_xml),array('Tidy_Transitional', 'Tidy_Proprietary'),array(),'-//W3C//DTD HTML 4.01 Transitional//EN','http://www.w3.org/TR/html4/loose.dtd');$this->doctypes->register('HTML 4.01 Strict',false,array_merge($common, $non_xml),array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),array(),'-//W3C//DTD HTML 4.01//EN','http://www.w3.org/TR/html4/strict.dtd');$this->doctypes->register('XHTML 1.0 Transitional',true,array_merge($common, $transitional, $xml, $non_xml),array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),array(),'-//W3C//DTD XHTML 1.0 Transitional//EN','http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd');$this->doctypes->register('XHTML 1.0 Strict',true,array_merge($common, $xml, $non_xml),array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),array(),'-//W3C//DTD XHTML 1.0 Strict//EN','http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');$this->doctypes->register('XHTML 1.1',true,// Iframe is a real XHTML 1.1 module, despite being// "transitional"!array_merge($common, $xml, array('Ruby', 'Iframe')),array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1array(),'-//W3C//DTD XHTML 1.1//EN','http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd');}/*** Registers a module to the recognized module list, useful for* overloading pre-existing modules.* @param $module Mixed: string module name, with or without* HTMLPurifier_HTMLModule prefix, or instance of* subclass of HTMLPurifier_HTMLModule.* @param $overload Boolean whether or not to overload previous modules.* If this is not set, and you do overload a module,* HTML Purifier will complain with a warning.* @note This function will not call autoload, you must instantiate* (and thus invoke) autoload outside the method.* @note If a string is passed as a module name, different variants* will be tested in this order:* - Check for HTMLPurifier_HTMLModule_$name* - Check all prefixes with $name in order they were added* - Check for literal object name* - Throw fatal error* If your object name collides with an internal class, specify* your module manually. All modules must have been included* externally: registerModule will not perform inclusions for you!*/public function registerModule($module, $overload = false){if (is_string($module)) {// attempt to load the module$original_module = $module;$ok = false;foreach ($this->prefixes as $prefix) {$module = $prefix . $original_module;if (class_exists($module)) {$ok = true;break;}}if (!$ok) {$module = $original_module;if (!class_exists($module)) {trigger_error($original_module . ' module does not exist',E_USER_ERROR);return;}}$module = new $module();}if (empty($module->name)) {trigger_error('Module instance of ' . get_class($module) . ' must have name');return;}if (!$overload && isset($this->registeredModules[$module->name])) {trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);}$this->registeredModules[$module->name] = $module;}/*** Adds a module to the current doctype by first registering it,* and then tacking it on to the active doctype*/public function addModule($module){$this->registerModule($module);if (is_object($module)) {$module = $module->name;}$this->userModules[] = $module;}/*** Adds a class prefix that registerModule() will use to resolve a* string name to a concrete class*/public function addPrefix($prefix){$this->prefixes[] = $prefix;}/*** Performs processing on modules, after being called you may* use getElement() and getElements()* @param HTMLPurifier_Config $config*/public function setup($config){$this->trusted = $config->get('HTML.Trusted');// generate$this->doctype = $this->doctypes->make($config);$modules = $this->doctype->modules;// take out the default modules that aren't allowed$lookup = $config->get('HTML.AllowedModules');$special_cases = $config->get('HTML.CoreModules');if (is_array($lookup)) {foreach ($modules as $k => $m) {if (isset($special_cases[$m])) {continue;}if (!isset($lookup[$m])) {unset($modules[$k]);}}}// custom modulesif ($config->get('HTML.Proprietary')) {$modules[] = 'Proprietary';}if ($config->get('HTML.SafeObject')) {$modules[] = 'SafeObject';}if ($config->get('HTML.SafeEmbed')) {$modules[] = 'SafeEmbed';}if ($config->get('HTML.SafeScripting') !== array()) {$modules[] = 'SafeScripting';}if ($config->get('HTML.Nofollow')) {$modules[] = 'Nofollow';}if ($config->get('HTML.TargetBlank')) {$modules[] = 'TargetBlank';}// NB: HTML.TargetNoreferrer and HTML.TargetNoopener must be AFTER HTML.TargetBlank// so that its post-attr-transform gets run afterwards.if ($config->get('HTML.TargetNoreferrer')) {$modules[] = 'TargetNoreferrer';}if ($config->get('HTML.TargetNoopener')) {$modules[] = 'TargetNoopener';}// merge in custom modules$modules = array_merge($modules, $this->userModules);foreach ($modules as $module) {$this->processModule($module);$this->modules[$module]->setup($config);}foreach ($this->doctype->tidyModules as $module) {$this->processModule($module);$this->modules[$module]->setup($config);}// prepare any injectorsforeach ($this->modules as $module) {$n = array();foreach ($module->info_injector as $injector) {if (!is_object($injector)) {$class = "HTMLPurifier_Injector_$injector";$injector = new $class;}$n[$injector->name] = $injector;}$module->info_injector = $n;}// setup lookup table based on all valid modulesforeach ($this->modules as $module) {foreach ($module->info as $name => $def) {if (!isset($this->elementLookup[$name])) {$this->elementLookup[$name] = array();}$this->elementLookup[$name][] = $module->name;}}// note the different choice$this->contentSets = new HTMLPurifier_ContentSets(// content set assembly deals with all possible modules,// not just ones deemed to be "safe"$this->modules);$this->attrCollections = new HTMLPurifier_AttrCollections($this->attrTypes,// there is no way to directly disable a global attribute,// but using AllowedAttributes or simply not including// the module in your custom doctype should be sufficient$this->modules);}/*** Takes a module and adds it to the active module collection,* registering it if necessary.*/public function processModule($module){if (!isset($this->registeredModules[$module]) || is_object($module)) {$this->registerModule($module);}$this->modules[$module] = $this->registeredModules[$module];}/*** Retrieves merged element definitions.* @return Array of HTMLPurifier_ElementDef*/public function getElements(){$elements = array();foreach ($this->modules as $module) {if (!$this->trusted && !$module->safe) {continue;}foreach ($module->info as $name => $v) {if (isset($elements[$name])) {continue;}$elements[$name] = $this->getElement($name);}}// remove dud elements, this happens when an element that// appeared to be safe actually wasn'tforeach ($elements as $n => $v) {if ($v === false) {unset($elements[$n]);}}return $elements;}/*** Retrieves a single merged element definition* @param string $name Name of element* @param bool $trusted Boolean trusted overriding parameter: set to true* if you want the full version of an element* @return HTMLPurifier_ElementDef Merged HTMLPurifier_ElementDef* @note You may notice that modules are getting iterated over twice (once* in getElements() and once here). This* is because*/public function getElement($name, $trusted = null){if (!isset($this->elementLookup[$name])) {return false;}// setup global state variables$def = false;if ($trusted === null) {$trusted = $this->trusted;}// iterate through each module that has registered itself to this// elementforeach ($this->elementLookup[$name] as $module_name) {$module = $this->modules[$module_name];// refuse to create/merge from a module that is deemed unsafe--// pretend the module doesn't exist--when trusted mode is not on.if (!$trusted && !$module->safe) {continue;}// clone is used because, ideally speaking, the original// definition should not be modified. Usually, this will// make no difference, but for consistency's sake$new_def = clone $module->info[$name];if (!$def && $new_def->standalone) {$def = $new_def;} elseif ($def) {// This will occur even if $new_def is standalone. In practice,// this will usually result in a full replacement.$def->mergeIn($new_def);} else {// :TODO:// non-standalone definitions that don't have a standalone// to merge into could be deferred to the end// HOWEVER, it is perfectly valid for a non-standalone// definition to lack a standalone definition, even// after all processing: this allows us to safely// specify extra attributes for elements that may not be// enabled all in one place. In particular, this might// be the case for trusted elements. WARNING: care must// be taken that the /extra/ definitions are all safe.continue;}// attribute value expansions$this->attrCollections->performInclusions($def->attr);$this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);// descendants_are_inline, for ChildDef_Chameleonif (is_string($def->content_model) &&strpos($def->content_model, 'Inline') !== false) {if ($name != 'del' && $name != 'ins') {// this is for you, ins/del$def->descendants_are_inline = true;}}$this->contentSets->generateChildDef($def, $module);}// This can occur if there is a blank definition, but no base to// mix it in withif (!$def) {return false;}// add information on required attributesforeach ($def->attr as $attr_name => $attr_def) {if ($attr_def->required) {$def->required_attr[] = $attr_name;}}return $def;}}/*** Component of HTMLPurifier_AttrContext that accumulates IDs to prevent dupes* @note In Slashdot-speak, dupe means duplicate.* @note The default constructor does not accept $config or $context objects:* use must use the static build() factory method to perform initialization.*/class HTMLPurifier_IDAccumulator{/*** Lookup table of IDs we've accumulated.* @public*/public $ids = array();/*** Builds an IDAccumulator, also initializing the default blacklist* @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config* @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context* @return HTMLPurifier_IDAccumulator Fully initialized HTMLPurifier_IDAccumulator*/public static function build($config, $context){$id_accumulator = new HTMLPurifier_IDAccumulator();$id_accumulator->load($config->get('Attr.IDBlacklist'));return $id_accumulator;}/*** Add an ID to the lookup table.* @param string $id ID to be added.* @return bool status, true if success, false if there's a dupe*/public function add($id){if (isset($this->ids[$id])) {return false;}return $this->ids[$id] = true;}/*** Load a list of IDs into the lookup table* @param $array_of_ids Array of IDs to load* @note This function doesn't care about duplicates*/public function load($array_of_ids){foreach ($array_of_ids as $id) {$this->ids[$id] = true;}}}/*** Injects tokens into the document while parsing for well-formedness.* This enables "formatter-like" functionality such as auto-paragraphing,* smiley-ification and linkification to take place.** A note on how handlers create changes; this is done by assigning a new* value to the $token reference. These values can take a variety of forms and* are best described HTMLPurifier_Strategy_MakeWellFormed->processToken()* documentation.** @todo Allow injectors to request a re-run on their output. This* would help if an operation is recursive.*/abstract class HTMLPurifier_Injector{/*** Advisory name of injector, this is for friendly error messages.* @type string*/public $name;/*** @type HTMLPurifier_HTMLDefinition*/protected $htmlDefinition;/*** Reference to CurrentNesting variable in Context. This is an array* list of tokens that we are currently "inside"* @type array*/protected $currentNesting;/*** Reference to current token.* @type HTMLPurifier_Token*/protected $currentToken;/*** Reference to InputZipper variable in Context.* @type HTMLPurifier_Zipper*/protected $inputZipper;/*** Array of elements and attributes this injector creates and therefore* need to be allowed by the definition. Takes form of* array('element' => array('attr', 'attr2'), 'element2')* @type array*/public $needed = array();/*** Number of elements to rewind backwards (relative).* @type bool|int*/protected $rewindOffset = false;/*** Rewind to a spot to re-perform processing. This is useful if you* deleted a node, and now need to see if this change affected any* earlier nodes. Rewinding does not affect other injectors, and can* result in infinite loops if not used carefully.* @param bool|int $offset* @warning HTML Purifier will prevent you from fast-forwarding with this* function.*/public function rewindOffset($offset){$this->rewindOffset = $offset;}/*** Retrieves rewind offset, and then unsets it.* @return bool|int*/public function getRewindOffset(){$r = $this->rewindOffset;$this->rewindOffset = false;return $r;}/*** Prepares the injector by giving it the config and context objects:* this allows references to important variables to be made within* the injector. This function also checks if the HTML environment* will work with the Injector (see checkNeeded()).* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool|string Boolean false if success, string of missing needed element/attribute if failure*/public function prepare($config, $context){$this->htmlDefinition = $config->getHTMLDefinition();// Even though this might fail, some unit tests ignore this and// still test checkNeeded, so be careful. Maybe get rid of that// dependency.$result = $this->checkNeeded($config);if ($result !== false) {return $result;}$this->currentNesting =& $context->get('CurrentNesting');$this->currentToken =& $context->get('CurrentToken');$this->inputZipper =& $context->get('InputZipper');return false;}/*** This function checks if the HTML environment* will work with the Injector: if p tags are not allowed, the* Auto-Paragraphing injector should not be enabled.* @param HTMLPurifier_Config $config* @return bool|string Boolean false if success, string of missing needed element/attribute if failure*/public function checkNeeded($config){$def = $config->getHTMLDefinition();foreach ($this->needed as $element => $attributes) {if (is_int($element)) {$element = $attributes;}if (!isset($def->info[$element])) {return $element;}if (!is_array($attributes)) {continue;}foreach ($attributes as $name) {if (!isset($def->info[$element]->attr[$name])) {return "$element.$name";}}}return false;}/*** Tests if the context node allows a certain element* @param string $name Name of element to test for* @return bool True if element is allowed, false if it is not*/public function allowsElement($name){if (!empty($this->currentNesting)) {$parent_token = array_pop($this->currentNesting);$this->currentNesting[] = $parent_token;$parent = $this->htmlDefinition->info[$parent_token->name];} else {$parent = $this->htmlDefinition->info_parent_def;}if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {return false;}// check for exclusionif (!empty($this->currentNesting)) {for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {$node = $this->currentNesting[$i];$def = $this->htmlDefinition->info[$node->name];if (isset($def->excludes[$name])) {return false;}}}return true;}/*** Iterator function, which starts with the next token and continues until* you reach the end of the input tokens.* @warning Please prevent previous references from interfering with this* functions by setting $i = null beforehand!* @param int $i Current integer index variable for inputTokens* @param HTMLPurifier_Token $current Current token variable.* Do NOT use $token, as that variable is also a reference* @return bool*/protected function forward(&$i, &$current){if ($i === null) {$i = count($this->inputZipper->back) - 1;} else {$i--;}if ($i < 0) {return false;}$current = $this->inputZipper->back[$i];return true;}/*** Similar to _forward, but accepts a third parameter $nesting (which* should be initialized at 0) and stops when we hit the end tag* for the node $this->inputIndex starts in.* @param int $i Current integer index variable for inputTokens* @param HTMLPurifier_Token $current Current token variable.* Do NOT use $token, as that variable is also a reference* @param int $nesting* @return bool*/protected function forwardUntilEndToken(&$i, &$current, &$nesting){$result = $this->forward($i, $current);if (!$result) {return false;}if ($nesting === null) {$nesting = 0;}if ($current instanceof HTMLPurifier_Token_Start) {$nesting++;} elseif ($current instanceof HTMLPurifier_Token_End) {if ($nesting <= 0) {return false;}$nesting--;}return true;}/*** Iterator function, starts with the previous token and continues until* you reach the beginning of input tokens.* @warning Please prevent previous references from interfering with this* functions by setting $i = null beforehand!* @param int $i Current integer index variable for inputTokens* @param HTMLPurifier_Token $current Current token variable.* Do NOT use $token, as that variable is also a reference* @return bool*/protected function backward(&$i, &$current){if ($i === null) {$i = count($this->inputZipper->front) - 1;} else {$i--;}if ($i < 0) {return false;}$current = $this->inputZipper->front[$i];return true;}/*** Handler that is called when a text token is processed*/public function handleText(&$token){}/*** Handler that is called when a start or empty token is processed*/public function handleElement(&$token){}/*** Handler that is called when an end token is processed*/public function handleEnd(&$token){$this->notifyEnd($token);}/*** Notifier that is called when an end token is processed* @param HTMLPurifier_Token $token Current token variable.* @note This differs from handlers in that the token is read-only* @deprecated*/public function notifyEnd($token){}}/*** Represents a language and defines localizable string formatting and* other functions, as well as the localized messages for HTML Purifier.*/class HTMLPurifier_Language{/*** ISO 639 language code of language. Prefers shortest possible version.* @type string*/public $code = 'en';/*** Fallback language code.* @type bool|string*/public $fallback = false;/*** Array of localizable messages.* @type array*/public $messages = array();/*** Array of localizable error codes.* @type array*/public $errorNames = array();/*** True if no message file was found for this language, so English* is being used instead. Check this if you'd like to notify the* user that they've used a non-supported language.* @type bool*/public $error = false;/*** Has the language object been loaded yet?* @type bool* @todo Make it private, fix usage in HTMLPurifier_LanguageTest*/public $_loaded = false;/*** @type HTMLPurifier_Config*/protected $config;/*** @type HTMLPurifier_Context*/protected $context;/*** @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context*/public function __construct($config, $context){$this->config = $config;$this->context = $context;}/*** Loads language object with necessary info from factory cache* @note This is a lazy loader*/public function load(){if ($this->_loaded) {return;}$factory = HTMLPurifier_LanguageFactory::instance();$factory->loadLanguage($this->code);foreach ($factory->keys as $key) {$this->$key = $factory->cache[$this->code][$key];}$this->_loaded = true;}/*** Retrieves a localised message.* @param string $key string identifier of message* @return string localised message*/public function getMessage($key){if (!$this->_loaded) {$this->load();}if (!isset($this->messages[$key])) {return "[$key]";}return $this->messages[$key];}/*** Retrieves a localised error name.* @param int $int error number, corresponding to PHP's error reporting* @return string localised message*/public function getErrorName($int){if (!$this->_loaded) {$this->load();}if (!isset($this->errorNames[$int])) {return "[Error: $int]";}return $this->errorNames[$int];}/*** Converts an array list into a string readable representation* @param array $array* @return string*/public function listify($array){$sep = $this->getMessage('Item separator');$sep_last = $this->getMessage('Item separator last');$ret = '';for ($i = 0, $c = count($array); $i < $c; $i++) {if ($i == 0) {} elseif ($i + 1 < $c) {$ret .= $sep;} else {$ret .= $sep_last;}$ret .= $array[$i];}return $ret;}/*** Formats a localised message with passed parameters* @param string $key string identifier of message* @param array $args Parameters to substitute in* @return string localised message* @todo Implement conditionals? Right now, some messages make* reference to line numbers, but those aren't always available*/public function formatMessage($key, $args = array()){if (!$this->_loaded) {$this->load();}if (!isset($this->messages[$key])) {return "[$key]";}$raw = $this->messages[$key];$subst = array();$generator = false;foreach ($args as $i => $value) {if (is_object($value)) {if ($value instanceof HTMLPurifier_Token) {// factor this out some timeif (!$generator) {$generator = $this->context->get('Generator');}if (isset($value->name)) {$subst['$'.$i.'.Name'] = $value->name;}if (isset($value->data)) {$subst['$'.$i.'.Data'] = $value->data;}$subst['$'.$i.'.Compact'] =$subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);// a more complex algorithm for compact representation// could be introduced for all types of tokens. This// may need to be factored out into a dedicated classif (!empty($value->attr)) {$stripped_token = clone $value;$stripped_token->attr = array();$subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);}$subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';}continue;} elseif (is_array($value)) {$keys = array_keys($value);if (array_keys($keys) === $keys) {// list$subst['$'.$i] = $this->listify($value);} else {// associative array// no $i implementation yet, sorry$subst['$'.$i.'.Keys'] = $this->listify($keys);$subst['$'.$i.'.Values'] = $this->listify(array_values($value));}continue;}$subst['$' . $i] = $value;}return strtr($raw, $subst);}}/*** Class responsible for generating HTMLPurifier_Language objects, managing* caching and fallbacks.* @note Thanks to MediaWiki for the general logic, although this version* has been entirely rewritten* @todo Serialized cache for languages*/class HTMLPurifier_LanguageFactory{/*** Cache of language code information used to load HTMLPurifier_Language objects.* Structure is: $factory->cache[$language_code][$key] = $value* @type array*/public $cache;/*** Valid keys in the HTMLPurifier_Language object. Designates which* variables to slurp out of a message file.* @type array*/public $keys = array('fallback', 'messages', 'errorNames');/*** Instance to validate language codes.* @type HTMLPurifier_AttrDef_Lang**/protected $validator;/*** Cached copy of dirname(__FILE__), directory of current file without* trailing slash.* @type string*/protected $dir;/*** Keys whose contents are a hash map and can be merged.* @type array*/protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);/*** Keys whose contents are a list and can be merged.* @value array lookup*/protected $mergeable_keys_list = array();/*** Retrieve sole instance of the factory.* @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with,* or bool true to reset to default factory.* @return HTMLPurifier_LanguageFactory*/public static function instance($prototype = null){static $instance = null;if ($prototype !== null) {$instance = $prototype;} elseif ($instance === null || $prototype == true) {$instance = new HTMLPurifier_LanguageFactory();$instance->setup();}return $instance;}/*** Sets up the singleton, much like a constructor* @note Prevents people from getting this outside of the singleton*/public function setup(){$this->validator = new HTMLPurifier_AttrDef_Lang();$this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';}/*** Creates a language object, handles class fallbacks* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @param bool|string $code Code to override configuration with. Private parameter.* @return HTMLPurifier_Language*/public function create($config, $context, $code = false){// validate language codeif ($code === false) {$code = $this->validator->validate($config->get('Core.Language'),$config,$context);} else {$code = $this->validator->validate($code, $config, $context);}if ($code === false) {$code = 'en'; // malformed code becomes English}$pcode = str_replace('-', '_', $code); // make valid PHP classnamestatic $depth = 0; // recursion protectionif ($code == 'en') {$lang = new HTMLPurifier_Language($config, $context);} else {$class = 'HTMLPurifier_Language_' . $pcode;$file = $this->dir . '/Language/classes/' . $code . '.php';if (file_exists($file) || class_exists($class, false)) {$lang = new $class($config, $context);} else {// Go fallback$raw_fallback = $this->getFallbackFor($code);$fallback = $raw_fallback ? $raw_fallback : 'en';$depth++;$lang = $this->create($config, $context, $fallback);if (!$raw_fallback) {$lang->error = true;}$depth--;}}$lang->code = $code;return $lang;}/*** Returns the fallback language for language* @note Loads the original language into cache* @param string $code language code* @return string|bool*/public function getFallbackFor($code){$this->loadLanguage($code);return $this->cache[$code]['fallback'];}/*** Loads language into the cache, handles message file and fallbacks* @param string $code language code*/public function loadLanguage($code){static $languages_seen = array(); // recursion guard// abort if we've already loaded itif (isset($this->cache[$code])) {return;}// generate filename$filename = $this->dir . '/Language/messages/' . $code . '.php';// default fallback : may be overwritten by the ensuing include$fallback = ($code != 'en') ? 'en' : false;// load primary localisationif (!file_exists($filename)) {// skip the include: will rely solely on fallback$filename = $this->dir . '/Language/messages/en.php';$cache = array();} else {include $filename;$cache = compact($this->keys);}// load fallback localisationif (!empty($fallback)) {// infinite recursion guardif (isset($languages_seen[$code])) {trigger_error('Circular fallback reference in language ' .$code,E_USER_ERROR);$fallback = 'en';}$language_seen[$code] = true;// load the fallback recursively$this->loadLanguage($fallback);$fallback_cache = $this->cache[$fallback];// merge fallback with current languageforeach ($this->keys as $key) {if (isset($cache[$key]) && isset($fallback_cache[$key])) {if (isset($this->mergeable_keys_map[$key])) {$cache[$key] = $cache[$key] + $fallback_cache[$key];} elseif (isset($this->mergeable_keys_list[$key])) {$cache[$key] = array_merge($fallback_cache[$key], $cache[$key]);}} else {$cache[$key] = $fallback_cache[$key];}}}// save to cache for later retrieval$this->cache[$code] = $cache;return;}}/*** Represents a measurable length, with a string numeric magnitude* and a unit. This object is immutable.*/class HTMLPurifier_Length{/*** String numeric magnitude.* @type string*/protected $n;/*** String unit. False is permitted if $n = 0.* @type string|bool*/protected $unit;/*** Whether or not this length is valid. Null if not calculated yet.* @type bool*/protected $isValid;/*** Array Lookup array of units recognized by CSS 3* @type array*/protected static $allowedUnits = array('em' => true, 'ex' => true, 'px' => true, 'in' => true,'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true,'ch' => true, 'rem' => true, 'vw' => true, 'vh' => true,'vmin' => true, 'vmax' => true);/*** @param string $n Magnitude* @param bool|string $u Unit*/public function __construct($n = '0', $u = false){$this->n = (string) $n;$this->unit = $u !== false ? (string) $u : false;}/*** @param string $s Unit string, like '2em' or '3.4in'* @return HTMLPurifier_Length* @warning Does not perform validation.*/public static function make($s){if ($s instanceof HTMLPurifier_Length) {return $s;}$n_length = strspn($s, '1234567890.+-');$n = substr($s, 0, $n_length);$unit = substr($s, $n_length);if ($unit === '') {$unit = false;}return new HTMLPurifier_Length($n, $unit);}/*** Validates the number and unit.* @return bool*/protected function validate(){// Special case:if ($this->n === '+0' || $this->n === '-0') {$this->n = '0';}if ($this->n === '0' && $this->unit === false) {return true;}if (!ctype_lower($this->unit)) {$this->unit = strtolower($this->unit);}if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {return false;}// Hack:$def = new HTMLPurifier_AttrDef_CSS_Number();$result = $def->validate($this->n, false, false);if ($result === false) {return false;}$this->n = $result;return true;}/*** Returns string representation of number.* @return string*/public function toString(){if (!$this->isValid()) {return false;}return $this->n . $this->unit;}/*** Retrieves string numeric magnitude.* @return string*/public function getN(){return $this->n;}/*** Retrieves string unit.* @return string*/public function getUnit(){return $this->unit;}/*** Returns true if this length unit is valid.* @return bool*/public function isValid(){if ($this->isValid === null) {$this->isValid = $this->validate();}return $this->isValid;}/*** Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.* @param HTMLPurifier_Length $l* @return int* @warning If both values are too large or small, this calculation will* not work properly*/public function compareTo($l){if ($l === false) {return false;}if ($l->unit !== $this->unit) {$converter = new HTMLPurifier_UnitConverter();$l = $converter->convert($l, $this->unit);if ($l === false) {return false;}}return $this->n - $l->n;}}/*** Forgivingly lexes HTML (SGML-style) markup into tokens.** A lexer parses a string of SGML-style markup and converts them into* corresponding tokens. It doesn't check for well-formedness, although its* internal mechanism may make this automatic (such as the case of* HTMLPurifier_Lexer_DOMLex). There are several implementations to choose* from.** A lexer is HTML-oriented: it might work with XML, but it's not* recommended, as we adhere to a subset of the specification for optimization* reasons. This might change in the future. Also, most tokenizers are not* expected to handle DTDs or PIs.** This class should not be directly instantiated, but you may use create() to* retrieve a default copy of the lexer. Being a supertype, this class* does not actually define any implementation, but offers commonly used* convenience functions for subclasses.** @note The unit tests will instantiate this class for testing purposes, as* many of the utility functions require a class to be instantiated.* This means that, even though this class is not runnable, it will* not be declared abstract.** @par** @note* We use tokens rather than create a DOM representation because DOM would:** @par* -# Require more processing and memory to create,* -# Is not streamable, and* -# Has the entire document structure (html and body not needed).** @par* However, DOM is helpful in that it makes it easy to move around nodes* without a lot of lookaheads to see when a tag is closed. This is a* limitation of the token system and some workarounds would be nice.*/class HTMLPurifier_Lexer{/*** Whether or not this lexer implements line-number/column-number tracking.* If it does, set to true.*/public $tracksLineNumbers = false;// -- STATIC ----------------------------------------------------------/*** Retrieves or sets the default Lexer as a Prototype Factory.** By default HTMLPurifier_Lexer_DOMLex will be returned. There are* a few exceptions involving special features that only DirectLex* implements.** @note The behavior of this class has changed, rather than accepting* a prototype object, it now accepts a configuration object.* To specify your own prototype, set %Core.LexerImpl to it.* This change in behavior de-singletonizes the lexer object.** @param HTMLPurifier_Config $config* @return HTMLPurifier_Lexer* @throws HTMLPurifier_Exception*/public static function create($config){if (!($config instanceof HTMLPurifier_Config)) {$lexer = $config;trigger_error("Passing a prototype toHTMLPurifier_Lexer::create() is deprecated, please insteaduse %Core.LexerImpl",E_USER_WARNING);} else {$lexer = $config->get('Core.LexerImpl');}$needs_tracking =$config->get('Core.MaintainLineNumbers') ||$config->get('Core.CollectErrors');$inst = null;if (is_object($lexer)) {$inst = $lexer;} else {if (is_null($lexer)) {do {// auto-detection algorithmif ($needs_tracking) {$lexer = 'DirectLex';break;}if (class_exists('DOMDocument', false) &&method_exists('DOMDocument', 'loadHTML') &&!extension_loaded('domxml')) {// check for DOM support, because while it's part of the// core, it can be disabled compile time. Also, the PECL// domxml extension overrides the default DOM, and is evil// and nasty and we shan't bother to support it$lexer = 'DOMLex';} else {$lexer = 'DirectLex';}} while (0);} // do..while so we can break// instantiate recognized string namesswitch ($lexer) {case 'DOMLex':$inst = new HTMLPurifier_Lexer_DOMLex();break;case 'DirectLex':$inst = new HTMLPurifier_Lexer_DirectLex();break;case 'PH5P':$inst = new HTMLPurifier_Lexer_PH5P();break;default:throw new HTMLPurifier_Exception("Cannot instantiate unrecognized Lexer type " .htmlspecialchars($lexer));}}if (!$inst) {throw new HTMLPurifier_Exception('No lexer was instantiated');}// once PHP DOM implements native line numbers, or we// hack out something using XSLT, remove this stipulationif ($needs_tracking && !$inst->tracksLineNumbers) {throw new HTMLPurifier_Exception('Cannot use lexer that does not support line numbers with ' .'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)');}return $inst;}// -- CONVENIENCE MEMBERS ---------------------------------------------public function __construct(){$this->_entity_parser = new HTMLPurifier_EntityParser();}/*** Most common entity to raw value conversion table for special entities.* @type array*/protected $_special_entity2str =array('"' => '"','&' => '&','<' => '<','>' => '>',''' => "'",''' => "'",''' => "'");public function parseText($string, $config) {return $this->parseData($string, false, $config);}public function parseAttr($string, $config) {return $this->parseData($string, true, $config);}/*** Parses special entities into the proper characters.** This string will translate escaped versions of the special characters* into the correct ones.** @param string $string String character data to be parsed.* @return string Parsed character data.*/public function parseData($string, $is_attr, $config){// following functions require at least one characterif ($string === '') {return '';}// subtracts amps that cannot possibly be escaped$num_amp = substr_count($string, '&') - substr_count($string, '& ') -($string[strlen($string) - 1] === '&' ? 1 : 0);if (!$num_amp) {return $string;} // abort if no entities$num_esc_amp = substr_count($string, '&');$string = strtr($string, $this->_special_entity2str);// code duplication for sake of optimization, see above$num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -($string[strlen($string) - 1] === '&' ? 1 : 0);if ($num_amp_2 <= $num_esc_amp) {return $string;}// hmm... now we have some uncommon entities. Use the callback.if ($config->get('Core.LegacyEntityDecoder')) {$string = $this->_entity_parser->substituteSpecialEntities($string);} else {if ($is_attr) {$string = $this->_entity_parser->substituteAttrEntities($string);} else {$string = $this->_entity_parser->substituteTextEntities($string);}}return $string;}/*** Lexes an HTML string into tokens.* @param $string String HTML.* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_Token[] array representation of HTML.*/public function tokenizeHTML($string, $config, $context){trigger_error('Call to abstract class', E_USER_ERROR);}/*** Translates CDATA sections into regular sections (through escaping).* @param string $string HTML string to process.* @return string HTML with CDATA sections escaped.*/protected static function escapeCDATA($string){return preg_replace_callback('/<!\[CDATA\[(.+?)\]\]>/s',array('HTMLPurifier_Lexer', 'CDATACallback'),$string);}/*** Special CDATA case that is especially convoluted for <script>* @param string $string HTML string to process.* @return string HTML with CDATA sections escaped.*/protected static function escapeCommentedCDATA($string){return preg_replace_callback('#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',array('HTMLPurifier_Lexer', 'CDATACallback'),$string);}/*** Special Internet Explorer conditional comments should be removed.* @param string $string HTML string to process.* @return string HTML with conditional comments removed.*/protected static function removeIEConditional($string){return preg_replace('#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', // probably should generalize for all strings'',$string);}/*** Callback function for escapeCDATA() that does the work.** @warning Though this is public in order to let the callback happen,* calling it directly is not recommended.* @param array $matches PCRE matches array, with index 0 the entire match* and 1 the inside of the CDATA section.* @return string Escaped internals of the CDATA section.*/protected static function CDATACallback($matches){// not exactly sure why the character set is needed, but whateverreturn htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');}/*** Takes a piece of HTML and normalizes it by converting entities, fixing* encoding, extracting bits, and other good stuff.* @param string $html HTML.* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return string* @todo Consider making protected*/public function normalize($html, $config, $context){// normalize newlines to \nif ($config->get('Core.NormalizeNewlines')) {$html = str_replace("\r\n", "\n", $html);$html = str_replace("\r", "\n", $html);}if ($config->get('HTML.Trusted')) {// escape convoluted CDATA$html = $this->escapeCommentedCDATA($html);}// escape CDATA$html = $this->escapeCDATA($html);$html = $this->removeIEConditional($html);// extract body from document if applicableif ($config->get('Core.ConvertDocumentToFragment')) {$e = false;if ($config->get('Core.CollectErrors')) {$e =& $context->get('ErrorCollector');}$new_html = $this->extractBody($html);if ($e && $new_html != $html) {$e->send(E_WARNING, 'Lexer: Extracted body');}$html = $new_html;}// expand entities that aren't the big fiveif ($config->get('Core.LegacyEntityDecoder')) {$html = $this->_entity_parser->substituteNonSpecialEntities($html);}// clean into wellformed UTF-8 string for an SGML context: this has// to be done after entity expansion because the entities sometimes// represent non-SGML characters (horror, horror!)$html = HTMLPurifier_Encoder::cleanUTF8($html);// if processing instructions are to removed, remove them nowif ($config->get('Core.RemoveProcessingInstructions')) {$html = preg_replace('#<\?.+?\?>#s', '', $html);}$hidden_elements = $config->get('Core.HiddenElements');if ($config->get('Core.AggressivelyRemoveScript') &&!($config->get('HTML.Trusted') || !$config->get('Core.RemoveScriptContents')|| empty($hidden_elements["script"]))) {$html = preg_replace('#<script[^>]*>.*?</script>#i', '', $html);}return $html;}/*** Takes a string of HTML (fragment or document) and returns the content* @todo Consider making protected*/public function extractBody($html){$matches = array();$result = preg_match('|(.*?)<body[^>]*>(.*)</body>|is', $html, $matches);if ($result) {// Make sure it's not in a comment$comment_start = strrpos($matches[1], '<!--');$comment_end = strrpos($matches[1], '-->');if ($comment_start === false ||($comment_end !== false && $comment_end > $comment_start)) {return $matches[2];}}return $html;}}/*** Abstract base node class that all others inherit from.** Why do we not use the DOM extension? (1) It is not always available,* (2) it has funny constraints on the data it can represent,* whereas we want a maximally flexible representation, and (3) its* interface is a bit cumbersome.*/abstract class HTMLPurifier_Node{/*** Line number of the start token in the source document* @type int*/public $line;/*** Column number of the start token in the source document. Null if unknown.* @type int*/public $col;/*** Lookup array of processing that this token is exempt from.* Currently, valid values are "ValidateAttributes".* @type array*/public $armor = array();/*** When true, this node should be ignored as non-existent.** Who is responsible for ignoring dead nodes? FixNesting is* responsible for removing them before passing on to child* validators.*/public $dead = false;/*** Returns a pair of start and end tokens, where the end token* is null if it is not necessary. Does not include children.* @type array*/abstract public function toTokenPair();}/*** Class that handles operations involving percent-encoding in URIs.** @warning* Be careful when reusing instances of PercentEncoder. The object* you use for normalize() SHOULD NOT be used for encode(), or* vice-versa.*/class HTMLPurifier_PercentEncoder{/*** Reserved characters to preserve when using encode().* @type array*/protected $preserve = array();/*** String of characters that should be preserved while using encode().* @param bool $preserve*/public function __construct($preserve = false){// unreserved letters, ought to const-ifyfor ($i = 48; $i <= 57; $i++) { // digits$this->preserve[$i] = true;}for ($i = 65; $i <= 90; $i++) { // upper-case$this->preserve[$i] = true;}for ($i = 97; $i <= 122; $i++) { // lower-case$this->preserve[$i] = true;}$this->preserve[45] = true; // Dash -$this->preserve[46] = true; // Period .$this->preserve[95] = true; // Underscore _$this->preserve[126]= true; // Tilde ~// extra letters not to escapeif ($preserve !== false) {for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {$this->preserve[ord($preserve[$i])] = true;}}}/*** Our replacement for urlencode, it encodes all non-reserved characters,* as well as any extra characters that were instructed to be preserved.* @note* Assumes that the string has already been normalized, making any* and all percent escape sequences valid. Percents will not be* re-escaped, regardless of their status in $preserve* @param string $string String to be encoded* @return string Encoded string.*/public function encode($string){$ret = '';for ($i = 0, $c = strlen($string); $i < $c; $i++) {if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) {$ret .= '%' . sprintf('%02X', $int);} else {$ret .= $string[$i];}}return $ret;}/*** Fix up percent-encoding by decoding unreserved characters and normalizing.* @warning This function is affected by $preserve, even though the* usual desired behavior is for this not to preserve those* characters. Be careful when reusing instances of PercentEncoder!* @param string $string String to normalize* @return string*/public function normalize($string){if ($string == '') {return '';}$parts = explode('%', $string);$ret = array_shift($parts);foreach ($parts as $part) {$length = strlen($part);if ($length < 2) {$ret .= '%25' . $part;continue;}$encoding = substr($part, 0, 2);$text = substr($part, 2);if (!ctype_xdigit($encoding)) {$ret .= '%25' . $part;continue;}$int = hexdec($encoding);if (isset($this->preserve[$int])) {$ret .= chr($int) . $text;continue;}$encoding = strtoupper($encoding);$ret .= '%' . $encoding . $text;}return $ret;}}/*** Generic property list implementation*/class HTMLPurifier_PropertyList{/*** Internal data-structure for properties.* @type array*/protected $data = array();/*** Parent plist.* @type HTMLPurifier_PropertyList*/protected $parent;/*** Cache.* @type array*/protected $cache;/*** @param HTMLPurifier_PropertyList $parent Parent plist*/public function __construct($parent = null){$this->parent = $parent;}/*** Recursively retrieves the value for a key* @param string $name* @throws HTMLPurifier_Exception*/public function get($name){if ($this->has($name)) {return $this->data[$name];}// possible performance bottleneck, convert to iterative if necessaryif ($this->parent) {return $this->parent->get($name);}throw new HTMLPurifier_Exception("Key '$name' not found");}/*** Sets the value of a key, for this plist* @param string $name* @param mixed $value*/public function set($name, $value){$this->data[$name] = $value;}/*** Returns true if a given key exists* @param string $name* @return bool*/public function has($name){return array_key_exists($name, $this->data);}/*** Resets a value to the value of it's parent, usually the default. If* no value is specified, the entire plist is reset.* @param string $name*/public function reset($name = null){if ($name == null) {$this->data = array();} else {unset($this->data[$name]);}}/*** Squashes this property list and all of its property lists into a single* array, and returns the array. This value is cached by default.* @param bool $force If true, ignores the cache and regenerates the array.* @return array*/public function squash($force = false){if ($this->cache !== null && !$force) {return $this->cache;}if ($this->parent) {return $this->cache = array_merge($this->parent->squash($force), $this->data);} else {return $this->cache = $this->data;}}/*** Returns the parent plist.* @return HTMLPurifier_PropertyList*/public function getParent(){return $this->parent;}/*** Sets the parent plist.* @param HTMLPurifier_PropertyList $plist Parent plist*/public function setParent($plist){$this->parent = $plist;}}/*** Property list iterator. Do not instantiate this class directly.*/class HTMLPurifier_PropertyListIterator extends FilterIterator{/*** @type int*/protected $l;/*** @type string*/protected $filter;/*** @param Iterator $iterator Array of data to iterate over* @param string $filter Optional prefix to only allow values of*/public function __construct(Iterator $iterator, $filter = null){parent::__construct($iterator);$this->l = strlen($filter);$this->filter = $filter;}/*** @return bool*/public function accept(){$key = $this->getInnerIterator()->key();if (strncmp($key, $this->filter, $this->l) !== 0) {return false;}return true;}}/*** A simple array-backed queue, based off of the classic Okasaki* persistent amortized queue. The basic idea is to maintain two* stacks: an input stack and an output stack. When the output* stack runs out, reverse the input stack and use it as the output* stack.** We don't use the SPL implementation because it's only supported* on PHP 5.3 and later.** Exercise: Prove that push/pop on this queue take amortized O(1) time.** Exercise: Extend this queue to be a deque, while preserving amortized* O(1) time. Some care must be taken on rebalancing to avoid quadratic* behaviour caused by repeatedly shuffling data from the input stack* to the output stack and back.*/class HTMLPurifier_Queue {private $input;private $output;public function __construct($input = array()) {$this->input = $input;$this->output = array();}/*** Shifts an element off the front of the queue.*/public function shift() {if (empty($this->output)) {$this->output = array_reverse($this->input);$this->input = array();}if (empty($this->output)) {return NULL;}return array_pop($this->output);}/*** Pushes an element onto the front of the queue.*/public function push($x) {array_push($this->input, $x);}/*** Checks if it's empty.*/public function isEmpty() {return empty($this->input) && empty($this->output);}}/*** Supertype for classes that define a strategy for modifying/purifying tokens.** While HTMLPurifier's core purpose is fixing HTML into something proper,* strategies provide plug points for extra configuration or even extra* features, such as custom tags, custom parsing of text, etc.*/abstract class HTMLPurifier_Strategy{/*** Executes the strategy on the tokens.** @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on.* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_Token[] Processed array of token objects.*/abstract public function execute($tokens, $config, $context);}/*** This is in almost every respect equivalent to an array except* that it keeps track of which keys were accessed.** @warning For the sake of backwards compatibility with early versions* of PHP 5, you must not use the $hash[$key] syntax; if you do* our version of offsetGet is never called.*/class HTMLPurifier_StringHash extends ArrayObject{/*** @type array*/protected $accessed = array();/*** Retrieves a value, and logs the access.* @param mixed $index* @return mixed*/public function offsetGet($index){$this->accessed[$index] = true;return parent::offsetGet($index);}/*** Returns a lookup array of all array indexes that have been accessed.* @return array in form array($index => true).*/public function getAccessed(){return $this->accessed;}/*** Resets the access array.*/public function resetAccessed(){$this->accessed = array();}}/*** Parses string hash files. File format is as such:** DefaultKeyValue* KEY: Value* KEY2: Value2* --MULTILINE-KEY--* Multiline* value.** Which would output something similar to:** array(* 'ID' => 'DefaultKeyValue',* 'KEY' => 'Value',* 'KEY2' => 'Value2',* 'MULTILINE-KEY' => "Multiline\nvalue.\n",* )** We use this as an easy to use file-format for configuration schema* files, but the class itself is usage agnostic.** You can use ---- to forcibly terminate parsing of a single string-hash;* this marker is used in multi string-hashes to delimit boundaries.*/class HTMLPurifier_StringHashParser{/*** @type string*/public $default = 'ID';/*** Parses a file that contains a single string-hash.* @param string $file* @return array*/public function parseFile($file){if (!file_exists($file)) {return false;}$fh = fopen($file, 'r');if (!$fh) {return false;}$ret = $this->parseHandle($fh);fclose($fh);return $ret;}/*** Parses a file that contains multiple string-hashes delimited by '----'* @param string $file* @return array*/public function parseMultiFile($file){if (!file_exists($file)) {return false;}$ret = array();$fh = fopen($file, 'r');if (!$fh) {return false;}while (!feof($fh)) {$ret[] = $this->parseHandle($fh);}fclose($fh);return $ret;}/*** Internal parser that acepts a file handle.* @note While it's possible to simulate in-memory parsing by using* custom stream wrappers, if such a use-case arises we should* factor out the file handle into its own class.* @param resource $fh File handle with pointer at start of valid string-hash* block.* @return array*/protected function parseHandle($fh){$state = false;$single = false;$ret = array();do {$line = fgets($fh);if ($line === false) {break;}$line = rtrim($line, "\n\r");if (!$state && $line === '') {continue;}if ($line === '----') {break;}if (strncmp('--#', $line, 3) === 0) {// Commentcontinue;} elseif (strncmp('--', $line, 2) === 0) {// Multiline declaration$state = trim($line, '- ');if (!isset($ret[$state])) {$ret[$state] = '';}continue;} elseif (!$state) {$single = true;if (strpos($line, ':') !== false) {// Single-line declarationlist($state, $line) = explode(':', $line, 2);$line = trim($line);} else {// Use default declaration$state = $this->default;}}if ($single) {$ret[$state] = $line;$single = false;$state = false;} else {$ret[$state] .= "$line\n";}} while (!feof($fh));return $ret;}}/*** Defines a mutation of an obsolete tag into a valid tag.*/abstract class HTMLPurifier_TagTransform{/*** Tag name to transform the tag to.* @type string*/public $transform_to;/*** Transforms the obsolete tag into the valid tag.* @param HTMLPurifier_Token_Tag $tag Tag to be transformed.* @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object* @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object*/abstract public function transform($tag, $config, $context);/*** Prepends CSS properties to the style attribute, creating the* attribute if it doesn't exist.* @warning Copied over from AttrTransform, be sure to keep in sync* @param array $attr Attribute array to process (passed by reference)* @param string $css CSS to prepend*/protected function prependCSS(&$attr, $css){$attr['style'] = isset($attr['style']) ? $attr['style'] : '';$attr['style'] = $css . $attr['style'];}}/*** Abstract base token class that all others inherit from.*/abstract class HTMLPurifier_Token{/*** Line number node was on in source document. Null if unknown.* @type int*/public $line;/*** Column of line node was on in source document. Null if unknown.* @type int*/public $col;/*** Lookup array of processing that this token is exempt from.* Currently, valid values are "ValidateAttributes" and* "MakeWellFormed_TagClosedError"* @type array*/public $armor = array();/*** Used during MakeWellFormed. See Note [Injector skips]* @type*/public $skip;/*** @type*/public $rewind;/*** @type*/public $carryover;/*** @param string $n* @return null|string*/public function __get($n){if ($n === 'type') {trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);switch (get_class($this)) {case 'HTMLPurifier_Token_Start':return 'start';case 'HTMLPurifier_Token_Empty':return 'empty';case 'HTMLPurifier_Token_End':return 'end';case 'HTMLPurifier_Token_Text':return 'text';case 'HTMLPurifier_Token_Comment':return 'comment';default:return null;}}}/*** Sets the position of the token in the source document.* @param int $l* @param int $c*/public function position($l = null, $c = null){$this->line = $l;$this->col = $c;}/*** Convenience function for DirectLex settings line/col position.* @param int $l* @param int $c*/public function rawPosition($l, $c){if ($c === -1) {$l++;}$this->line = $l;$this->col = $c;}/*** Converts a token into its corresponding node.*/abstract public function toNode();}/*** Factory for token generation.** @note Doing some benchmarking indicates that the new operator is much* slower than the clone operator (even discounting the cost of the* constructor). This class is for that optimization.* Other then that, there's not much point as we don't* maintain parallel HTMLPurifier_Token hierarchies (the main reason why* you'd want to use an abstract factory).* @todo Port DirectLex to use this*/class HTMLPurifier_TokenFactory{// p stands for prototype/*** @type HTMLPurifier_Token_Start*/private $p_start;/*** @type HTMLPurifier_Token_End*/private $p_end;/*** @type HTMLPurifier_Token_Empty*/private $p_empty;/*** @type HTMLPurifier_Token_Text*/private $p_text;/*** @type HTMLPurifier_Token_Comment*/private $p_comment;/*** Generates blank prototypes for cloning.*/public function __construct(){$this->p_start = new HTMLPurifier_Token_Start('', array());$this->p_end = new HTMLPurifier_Token_End('');$this->p_empty = new HTMLPurifier_Token_Empty('', array());$this->p_text = new HTMLPurifier_Token_Text('');$this->p_comment = new HTMLPurifier_Token_Comment('');}/*** Creates a HTMLPurifier_Token_Start.* @param string $name Tag name* @param array $attr Associative array of attributes* @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start*/public function createStart($name, $attr = array()){$p = clone $this->p_start;$p->__construct($name, $attr);return $p;}/*** Creates a HTMLPurifier_Token_End.* @param string $name Tag name* @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End*/public function createEnd($name){$p = clone $this->p_end;$p->__construct($name);return $p;}/*** Creates a HTMLPurifier_Token_Empty.* @param string $name Tag name* @param array $attr Associative array of attributes* @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty*/public function createEmpty($name, $attr = array()){$p = clone $this->p_empty;$p->__construct($name, $attr);return $p;}/*** Creates a HTMLPurifier_Token_Text.* @param string $data Data of text token* @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text*/public function createText($data){$p = clone $this->p_text;$p->__construct($data);return $p;}/*** Creates a HTMLPurifier_Token_Comment.* @param string $data Data of comment token* @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment*/public function createComment($data){$p = clone $this->p_comment;$p->__construct($data);return $p;}}/*** HTML Purifier's internal representation of a URI.* @note* Internal data-structures are completely escaped. If the data needs* to be used in a non-URI context (which is very unlikely), be sure* to decode it first. The URI may not necessarily be well-formed until* validate() is called.*/class HTMLPurifier_URI{/*** @type string*/public $scheme;/*** @type string*/public $userinfo;/*** @type string*/public $host;/*** @type int*/public $port;/*** @type string*/public $path;/*** @type string*/public $query;/*** @type string*/public $fragment;/*** @param string $scheme* @param string $userinfo* @param string $host* @param int $port* @param string $path* @param string $query* @param string $fragment* @note Automatically normalizes scheme and port*/public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment){$this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);$this->userinfo = $userinfo;$this->host = $host;$this->port = is_null($port) ? $port : (int)$port;$this->path = $path;$this->query = $query;$this->fragment = $fragment;}/*** Retrieves a scheme object corresponding to the URI's scheme/default* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI*/public function getSchemeObj($config, $context){$registry = HTMLPurifier_URISchemeRegistry::instance();if ($this->scheme !== null) {$scheme_obj = $registry->getScheme($this->scheme, $config, $context);if (!$scheme_obj) {return false;} // invalid scheme, clean it out} else {// no scheme: retrieve the default one$def = $config->getDefinition('URI');$scheme_obj = $def->getDefaultScheme($config, $context);if (!$scheme_obj) {if ($def->defaultScheme !== null) {// something funky happened to the default scheme objecttrigger_error('Default scheme object "' . $def->defaultScheme . '" was not readable',E_USER_WARNING);} // suppress error if it's nullreturn false;}}return $scheme_obj;}/*** Generic validation method applicable for all schemes. May modify* this URI in order to get it into a compliant form.* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool True if validation/filtering succeeds, false if failure*/public function validate($config, $context){// ABNF definitions from RFC 3986$chars_sub_delims = '!$&\'()*+,;=';$chars_gen_delims = ':/?#[]@';$chars_pchar = $chars_sub_delims . ':@';// validate hostif (!is_null($this->host)) {$host_def = new HTMLPurifier_AttrDef_URI_Host();$this->host = $host_def->validate($this->host, $config, $context);if ($this->host === false) {$this->host = null;}}// validate scheme// NOTE: It's not appropriate to check whether or not this// scheme is in our registry, since a URIFilter may convert a// URI that we don't allow into one we do. So instead, we just// check if the scheme can be dropped because there is no host// and it is our default scheme.if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {// support for relative paths is pretty abysmal when the// scheme is present, so axe it when possible$def = $config->getDefinition('URI');if ($def->defaultScheme === $this->scheme) {$this->scheme = null;}}// validate usernameif (!is_null($this->userinfo)) {$encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');$this->userinfo = $encoder->encode($this->userinfo);}// validate portif (!is_null($this->port)) {if ($this->port < 1 || $this->port > 65535) {$this->port = null;}}// validate path$segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');if (!is_null($this->host)) { // this catches $this->host === ''// path-abempty (hier and relative)// http://www.example.com/my/path// //www.example.com/my/path (looks odd, but works, and// recognized by most browsers)// (this set is valid or invalid on a scheme by scheme// basis, so we'll deal with it later)// file:///my/path// ///my/path$this->path = $segments_encoder->encode($this->path);} elseif ($this->path !== '') {if ($this->path[0] === '/') {// path-absolute (hier and relative)// http:/my/path// /my/pathif (strlen($this->path) >= 2 && $this->path[1] === '/') {// This could happen if both the host gets stripped// out// http://my/path// //my/path$this->path = '';} else {$this->path = $segments_encoder->encode($this->path);}} elseif (!is_null($this->scheme)) {// path-rootless (hier)// http:my/path// Short circuit evaluation means we don't need to check nz$this->path = $segments_encoder->encode($this->path);} else {// path-noscheme (relative)// my/path// (once again, not checking nz)$segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');$c = strpos($this->path, '/');if ($c !== false) {$this->path =$segment_nc_encoder->encode(substr($this->path, 0, $c)) .$segments_encoder->encode(substr($this->path, $c));} else {$this->path = $segment_nc_encoder->encode($this->path);}}} else {// path-empty (hier and relative)$this->path = ''; // just to be safe}// qf = query and fragment$qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');if (!is_null($this->query)) {$this->query = $qf_encoder->encode($this->query);}if (!is_null($this->fragment)) {$this->fragment = $qf_encoder->encode($this->fragment);}return true;}/*** Convert URI back to string* @return string URI appropriate for output*/public function toString(){// reconstruct authority$authority = null;// there is a rendering difference between a null authority// (http:foo-bar) and an empty string authority// (http:///foo-bar).if (!is_null($this->host)) {$authority = '';if (!is_null($this->userinfo)) {$authority .= $this->userinfo . '@';}$authority .= $this->host;if (!is_null($this->port)) {$authority .= ':' . $this->port;}}// Reconstruct the result// One might wonder about parsing quirks from browsers after// this reconstruction. Unfortunately, parsing behavior depends// on what *scheme* was employed (file:///foo is handled *very*// differently than http:///foo), so unfortunately we have to// defer to the schemes to do the right thing.$result = '';if (!is_null($this->scheme)) {$result .= $this->scheme . ':';}if (!is_null($authority)) {$result .= '//' . $authority;}$result .= $this->path;if (!is_null($this->query)) {$result .= '?' . $this->query;}if (!is_null($this->fragment)) {$result .= '#' . $this->fragment;}return $result;}/*** Returns true if this URL might be considered a 'local' URL given* the current context. This is true when the host is null, or* when it matches the host supplied to the configuration.** Note that this does not do any scheme checking, so it is mostly* only appropriate for metadata that doesn't care about protocol* security. isBenign is probably what you actually want.* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function isLocal($config, $context){if ($this->host === null) {return true;}$uri_def = $config->getDefinition('URI');if ($uri_def->host === $this->host) {return true;}return false;}/*** Returns true if this URL should be considered a 'benign' URL,* that is:** - It is a local URL (isLocal), and* - It has a equal or better level of security* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function isBenign($config, $context){if (!$this->isLocal($config, $context)) {return false;}$scheme_obj = $this->getSchemeObj($config, $context);if (!$scheme_obj) {return false;} // conservative approach$current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);if ($current_scheme_obj->secure) {if (!$scheme_obj->secure) {return false;}}return true;}}class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition{public $type = 'URI';protected $filters = array();protected $postFilters = array();protected $registeredFilters = array();/*** HTMLPurifier_URI object of the base specified at %URI.Base*/public $base;/*** String host to consider "home" base, derived off of $base*/public $host;/*** Name of default scheme based on %URI.DefaultScheme and %URI.Base*/public $defaultScheme;public function __construct(){$this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());$this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());$this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());$this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());$this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());$this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());$this->registerFilter(new HTMLPurifier_URIFilter_Munge());}public function registerFilter($filter){$this->registeredFilters[$filter->name] = $filter;}public function addFilter($filter, $config){$r = $filter->prepare($config);if ($r === false) return; // null is ok, for backwards compatif ($filter->post) {$this->postFilters[$filter->name] = $filter;} else {$this->filters[$filter->name] = $filter;}}protected function doSetup($config){$this->setupMemberVariables($config);$this->setupFilters($config);}protected function setupFilters($config){foreach ($this->registeredFilters as $name => $filter) {if ($filter->always_load) {$this->addFilter($filter, $config);} else {$conf = $config->get('URI.' . $name);if ($conf !== false && $conf !== null) {$this->addFilter($filter, $config);}}}unset($this->registeredFilters);}protected function setupMemberVariables($config){$this->host = $config->get('URI.Host');$base_uri = $config->get('URI.Base');if (!is_null($base_uri)) {$parser = new HTMLPurifier_URIParser();$this->base = $parser->parse($base_uri);$this->defaultScheme = $this->base->scheme;if (is_null($this->host)) $this->host = $this->base->host;}if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');}public function getDefaultScheme($config, $context){return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);}public function filter(&$uri, $config, $context){foreach ($this->filters as $name => $f) {$result = $f->filter($uri, $config, $context);if (!$result) return false;}return true;}public function postFilter(&$uri, $config, $context){foreach ($this->postFilters as $name => $f) {$result = $f->filter($uri, $config, $context);if (!$result) return false;}return true;}}/*** Chainable filters for custom URI processing.** These filters can perform custom actions on a URI filter object,* including transformation or blacklisting. A filter named Foo* must have a corresponding configuration directive %URI.Foo,* unless always_load is specified to be true.** The following contexts may be available while URIFilters are being* processed:** - EmbeddedURI: true if URI is an embedded resource that will* be loaded automatically on page load* - CurrentToken: a reference to the token that is currently* being processed* - CurrentAttr: the name of the attribute that is currently being* processed* - CurrentCSSProperty: the name of the CSS property that is* currently being processed (if applicable)** @warning This filter is called before scheme object validation occurs.* Make sure, if you require a specific scheme object, you* you check that it exists. This allows filters to convert* proprietary URI schemes into regular ones.*/abstract class HTMLPurifier_URIFilter{/*** Unique identifier of filter.* @type string*/public $name;/*** True if this filter should be run after scheme validation.* @type bool*/public $post = false;/*** True if this filter should always be loaded.* This permits a filter to be named Foo without the corresponding* %URI.Foo directive existing.* @type bool*/public $always_load = false;/*** Performs initialization for the filter. If the filter returns* false, this means that it shouldn't be considered active.* @param HTMLPurifier_Config $config* @return bool*/public function prepare($config){return true;}/*** Filter a URI object* @param HTMLPurifier_URI $uri Reference to URI object variable* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool Whether or not to continue processing: false indicates* URL is no good, true indicates continue processing. Note that* all changes are committed directly on the URI object*/abstract public function filter(&$uri, $config, $context);}/*** Parses a URI into the components and fragment identifier as specified* by RFC 3986.*/class HTMLPurifier_URIParser{/*** Instance of HTMLPurifier_PercentEncoder to do normalization with.*/protected $percentEncoder;public function __construct(){$this->percentEncoder = new HTMLPurifier_PercentEncoder();}/*** Parses a URI.* @param $uri string URI to parse* @return HTMLPurifier_URI representation of URI. This representation has* not been validated yet and may not conform to RFC.*/public function parse($uri){$uri = $this->percentEncoder->normalize($uri);// Regexp is as per Appendix B.// Note that ["<>] are an addition to the RFC's recommended// characters, because they represent external delimeters.$r_URI = '!'.'(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme'(//([^/?#"<>]*))?'. // 4. Authority'([^?#"<>]*)'. // 5. Path'(\?([^#"<>]*))?'. // 7. Query'(#([^"<>]*))?'. // 8. Fragment'!';$matches = array();$result = preg_match($r_URI, $uri, $matches);if (!$result) return false; // *really* invalid URI// seperate out parts$scheme = !empty($matches[1]) ? $matches[2] : null;$authority = !empty($matches[3]) ? $matches[4] : null;$path = $matches[5]; // always present, can be empty$query = !empty($matches[6]) ? $matches[7] : null;$fragment = !empty($matches[8]) ? $matches[9] : null;// further parse authorityif ($authority !== null) {$r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";$matches = array();preg_match($r_authority, $authority, $matches);$userinfo = !empty($matches[1]) ? $matches[2] : null;$host = !empty($matches[3]) ? $matches[3] : '';$port = !empty($matches[4]) ? (int) $matches[5] : null;} else {$port = $host = $userinfo = null;}return new HTMLPurifier_URI($scheme, $userinfo, $host, $port, $path, $query, $fragment);}}/*** Validator for the components of a URI for a specific scheme*/abstract class HTMLPurifier_URIScheme{/*** Scheme's default port (integer). If an explicit port number is* specified that coincides with the default port, it will be* elided.* @type int*/public $default_port = null;/*** Whether or not URIs of this scheme are locatable by a browser* http and ftp are accessible, while mailto and news are not.* @type bool*/public $browsable = false;/*** Whether or not data transmitted over this scheme is encrypted.* https is secure, http is not.* @type bool*/public $secure = false;/*** Whether or not the URI always uses <hier_part>, resolves edge cases* with making relative URIs absolute* @type bool*/public $hierarchical = false;/*** Whether or not the URI may omit a hostname when the scheme is* explicitly specified, ala file:///path/to/file. As of writing,* 'file' is the only scheme that browsers support his properly.* @type bool*/public $may_omit_host = false;/*** Validates the components of a URI for a specific scheme.* @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool success or failure*/abstract public function doValidate(&$uri, $config, $context);/*** Public interface for validating components of a URI. Performs a* bunch of default actions. Don't overload this method.* @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool success or failure*/public function validate(&$uri, $config, $context){if ($this->default_port == $uri->port) {$uri->port = null;}// kludge: browsers do funny things when the scheme but not the// authority is setif (!$this->may_omit_host &&// if the scheme is present, a missing host is always in error(!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||// if the scheme is not present, a *blank* host is in error,// since this translates into '///path' which most browsers// interpret as being 'http://path'.(is_null($uri->scheme) && $uri->host === '')) {do {if (is_null($uri->scheme)) {if (substr($uri->path, 0, 2) != '//') {$uri->host = null;break;}// URI is '////path', so we cannot nullify the// host to preserve semantics. Try expanding the// hostname instead (fall through)}// first see if we can manually insert a hostname$host = $config->get('URI.Host');if (!is_null($host)) {$uri->host = $host;} else {// we can't do anything sensible, reject the URL.return false;}} while (false);}return $this->doValidate($uri, $config, $context);}}/*** Registry for retrieving specific URI scheme validator objects.*/class HTMLPurifier_URISchemeRegistry{/*** Retrieve sole instance of the registry.* @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with,* or bool true to reset to default registry.* @return HTMLPurifier_URISchemeRegistry* @note Pass a registry object $prototype with a compatible interface and* the function will copy it and return it all further times.*/public static function instance($prototype = null){static $instance = null;if ($prototype !== null) {$instance = $prototype;} elseif ($instance === null || $prototype == true) {$instance = new HTMLPurifier_URISchemeRegistry();}return $instance;}/*** Cache of retrieved schemes.* @type HTMLPurifier_URIScheme[]*/protected $schemes = array();/*** Retrieves a scheme validator object* @param string $scheme String scheme name like http or mailto* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_URIScheme*/public function getScheme($scheme, $config, $context){if (!$config) {$config = HTMLPurifier_Config::createDefault();}// important, otherwise attacker could include arbitrary file$allowed_schemes = $config->get('URI.AllowedSchemes');if (!$config->get('URI.OverrideAllowedSchemes') &&!isset($allowed_schemes[$scheme])) {return;}if (isset($this->schemes[$scheme])) {return $this->schemes[$scheme];}if (!isset($allowed_schemes[$scheme])) {return;}$class = 'HTMLPurifier_URIScheme_' . $scheme;if (!class_exists($class)) {return;}$this->schemes[$scheme] = new $class();return $this->schemes[$scheme];}/*** Registers a custom scheme to the cache, bypassing reflection.* @param string $scheme Scheme name* @param HTMLPurifier_URIScheme $scheme_obj*/public function register($scheme, $scheme_obj){$this->schemes[$scheme] = $scheme_obj;}}/*** Class for converting between different unit-lengths as specified by* CSS.*/class HTMLPurifier_UnitConverter{const ENGLISH = 1;const METRIC = 2;const DIGITAL = 3;/*** Units information array. Units are grouped into measuring systems* (English, Metric), and are assigned an integer representing* the conversion factor between that unit and the smallest unit in* the system. Numeric indexes are actually magical constants that* encode conversion data from one system to the next, with a O(n^2)* constraint on memory (this is generally not a problem, since* the number of measuring systems is small.)*/protected static $units = array(self::ENGLISH => array('px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary'pt' => 4,'pc' => 48,'in' => 288,self::METRIC => array('pt', '0.352777778', 'mm'),),self::METRIC => array('mm' => 1,'cm' => 10,self::ENGLISH => array('mm', '2.83464567', 'pt'),),);/*** Minimum bcmath precision for output.* @type int*/protected $outputPrecision;/*** Bcmath precision for internal calculations.* @type int*/protected $internalPrecision;/*** Whether or not BCMath is available.* @type bool*/private $bcmath;public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false){$this->outputPrecision = $output_precision;$this->internalPrecision = $internal_precision;$this->bcmath = !$force_no_bcmath && function_exists('bcmul');}/*** Converts a length object of one unit into another unit.* @param HTMLPurifier_Length $length* Instance of HTMLPurifier_Length to convert. You must validate()* it before passing it here!* @param string $to_unit* Unit to convert to.* @return HTMLPurifier_Length|bool* @note* About precision: This conversion function pays very special* attention to the incoming precision of values and attempts* to maintain a number of significant figure. Results are* fairly accurate up to nine digits. Some caveats:* - If a number is zero-padded as a result of this significant* figure tracking, the zeroes will be eliminated.* - If a number contains less than four sigfigs ($outputPrecision)* and this causes some decimals to be excluded, those* decimals will be added on.*/public function convert($length, $to_unit){if (!$length->isValid()) {return false;}$n = $length->getN();$unit = $length->getUnit();if ($n === '0' || $unit === false) {return new HTMLPurifier_Length('0', false);}$state = $dest_state = false;foreach (self::$units as $k => $x) {if (isset($x[$unit])) {$state = $k;}if (isset($x[$to_unit])) {$dest_state = $k;}}if (!$state || !$dest_state) {return false;}// Some calculations about the initial precision of the number;// this will be useful when we need to do final rounding.$sigfigs = $this->getSigFigs($n);if ($sigfigs < $this->outputPrecision) {$sigfigs = $this->outputPrecision;}// BCMath's internal precision deals only with decimals. Use// our default if the initial number has no decimals, or increase// it by how ever many decimals, thus, the number of guard digits// will always be greater than or equal to internalPrecision.$log = (int)floor(log(abs($n), 10));$cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precisionfor ($i = 0; $i < 2; $i++) {// Determine what unit IN THIS SYSTEM we need to convert toif ($dest_state === $state) {// Simple conversion$dest_unit = $to_unit;} else {// Convert to the smallest unit, pending a system shift$dest_unit = self::$units[$state][$dest_state][0];}// Do the conversion if necessaryif ($dest_unit !== $unit) {$factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);$n = $this->mul($n, $factor, $cp);$unit = $dest_unit;}// Output was zero, so bail out early. Shouldn't ever happen.if ($n === '') {$n = '0';$unit = $to_unit;break;}// It was a simple conversion, so bail outif ($dest_state === $state) {break;}if ($i !== 0) {// Conversion failed! Apparently, the system we forwarded// to didn't have this unit. This should never happen!return false;}// Pre-condition: $i == 0// Perform conversion to next system of units$n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);$unit = self::$units[$state][$dest_state][2];$state = $dest_state;// One more loop around to convert the unit in the new system.}// Post-condition: $unit == $to_unitif ($unit !== $to_unit) {return false;}// Useful for debugging://echo "<pre>n";//echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";$n = $this->round($n, $sigfigs);if (strpos($n, '.') !== false) {$n = rtrim($n, '0');}$n = rtrim($n, '.');return new HTMLPurifier_Length($n, $unit);}/*** Returns the number of significant figures in a string number.* @param string $n Decimal number* @return int number of sigfigs*/public function getSigFigs($n){$n = ltrim($n, '0+-');$dp = strpos($n, '.'); // decimal positionif ($dp === false) {$sigfigs = strlen(rtrim($n, '0'));} else {$sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal characterif ($dp !== 0) {$sigfigs--;}}return $sigfigs;}/*** Adds two numbers, using arbitrary precision when available.* @param string $s1* @param string $s2* @param int $scale* @return string*/private function add($s1, $s2, $scale){if ($this->bcmath) {return bcadd($s1, $s2, $scale);} else {return $this->scale((float)$s1 + (float)$s2, $scale);}}/*** Multiples two numbers, using arbitrary precision when available.* @param string $s1* @param string $s2* @param int $scale* @return string*/private function mul($s1, $s2, $scale){if ($this->bcmath) {return bcmul($s1, $s2, $scale);} else {return $this->scale((float)$s1 * (float)$s2, $scale);}}/*** Divides two numbers, using arbitrary precision when available.* @param string $s1* @param string $s2* @param int $scale* @return string*/private function div($s1, $s2, $scale){if ($this->bcmath) {return bcdiv($s1, $s2, $scale);} else {return $this->scale((float)$s1 / (float)$s2, $scale);}}/*** Rounds a number according to the number of sigfigs it should have,* using arbitrary precision when available.* @param float $n* @param int $sigfigs* @return string*/private function round($n, $sigfigs){$new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1$rp = $sigfigs - $new_log - 1; // Number of decimal places needed$neg = $n < 0 ? '-' : ''; // Negative signif ($this->bcmath) {if ($rp >= 0) {$n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);$n = bcdiv($n, '1', $rp);} else {// This algorithm partially depends on the standardized// form of numbers that comes out of bcmath.$n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);$n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);}return $n;} else {return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);}}/*** Scales a float to $scale digits right of decimal point, like BCMath.* @param float $r* @param int $scale* @return string*/private function scale($r, $scale){if ($scale < 0) {// The f sprintf type doesn't support negative numbers, so we// need to cludge things manually. First get the string.$r = sprintf('%.0f', (float)$r);// Due to floating point precision loss, $r will more than likely// look something like 4652999999999.9234. We grab one more digit// than we need to precise from $r and then use that to round// appropriately.$precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);// Now we return it, truncating the zero that was rounded off.return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);}return sprintf('%.' . $scale . 'f', (float)$r);}}/*** Parses string representations into their corresponding native PHP* variable type. The base implementation does a simple type-check.*/class HTMLPurifier_VarParser{const C_STRING = 1;const ISTRING = 2;const TEXT = 3;const ITEXT = 4;const C_INT = 5;const C_FLOAT = 6;const C_BOOL = 7;const LOOKUP = 8;const ALIST = 9;const HASH = 10;const C_MIXED = 11;/*** Lookup table of allowed types. Mainly for backwards compatibility, but* also convenient for transforming string type names to the integer constants.*/public static $types = array('string' => self::C_STRING,'istring' => self::ISTRING,'text' => self::TEXT,'itext' => self::ITEXT,'int' => self::C_INT,'float' => self::C_FLOAT,'bool' => self::C_BOOL,'lookup' => self::LOOKUP,'list' => self::ALIST,'hash' => self::HASH,'mixed' => self::C_MIXED);/*** Lookup table of types that are string, and can have aliases or* allowed value lists.*/public static $stringTypes = array(self::C_STRING => true,self::ISTRING => true,self::TEXT => true,self::ITEXT => true,);/*** Validate a variable according to type.* It may return NULL as a valid type if $allow_null is true.** @param mixed $var Variable to validate* @param int $type Type of variable, see HTMLPurifier_VarParser->types* @param bool $allow_null Whether or not to permit null as a value* @return string Validated and type-coerced variable* @throws HTMLPurifier_VarParserException*/final public function parse($var, $type, $allow_null = false){if (is_string($type)) {if (!isset(HTMLPurifier_VarParser::$types[$type])) {throw new HTMLPurifier_VarParserException("Invalid type '$type'");} else {$type = HTMLPurifier_VarParser::$types[$type];}}$var = $this->parseImplementation($var, $type, $allow_null);if ($allow_null && $var === null) {return null;}// These are basic checks, to make sure nothing horribly wrong// happened in our implementations.switch ($type) {case (self::C_STRING):case (self::ISTRING):case (self::TEXT):case (self::ITEXT):if (!is_string($var)) {break;}if ($type == self::ISTRING || $type == self::ITEXT) {$var = strtolower($var);}return $var;case (self::C_INT):if (!is_int($var)) {break;}return $var;case (self::C_FLOAT):if (!is_float($var)) {break;}return $var;case (self::C_BOOL):if (!is_bool($var)) {break;}return $var;case (self::LOOKUP):case (self::ALIST):case (self::HASH):if (!is_array($var)) {break;}if ($type === self::LOOKUP) {foreach ($var as $k) {if ($k !== true) {$this->error('Lookup table contains value other than true');}}} elseif ($type === self::ALIST) {$keys = array_keys($var);if (array_keys($keys) !== $keys) {$this->error('Indices for list are not uniform');}}return $var;case (self::C_MIXED):return $var;default:$this->errorInconsistent(get_class($this), $type);}$this->errorGeneric($var, $type);}/*** Actually implements the parsing. Base implementation does not* do anything to $var. Subclasses should overload this!* @param mixed $var* @param int $type* @param bool $allow_null* @return string*/protected function parseImplementation($var, $type, $allow_null){return $var;}/*** Throws an exception.* @throws HTMLPurifier_VarParserException*/protected function error($msg){throw new HTMLPurifier_VarParserException($msg);}/*** Throws an inconsistency exception.* @note This should not ever be called. It would be called if we* extend the allowed values of HTMLPurifier_VarParser without* updating subclasses.* @param string $class* @param int $type* @throws HTMLPurifier_Exception*/protected function errorInconsistent($class, $type){throw new HTMLPurifier_Exception("Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) ." not implemented");}/*** Generic error for if a type didn't work.* @param mixed $var* @param int $type*/protected function errorGeneric($var, $type){$vtype = gettype($var);$this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");}/*** @param int $type* @return string*/public static function getTypeName($type){static $lookup;if (!$lookup) {// Lazy load the alternative lookup table$lookup = array_flip(HTMLPurifier_VarParser::$types);}if (!isset($lookup[$type])) {return 'unknown';}return $lookup[$type];}}/*** Exception type for HTMLPurifier_VarParser*/class HTMLPurifier_VarParserException extends HTMLPurifier_Exception{}/*** A zipper is a purely-functional data structure which contains* a focus that can be efficiently manipulated. It is known as* a "one-hole context". This mutable variant implements a zipper* for a list as a pair of two arrays, laid out as follows:** Base list: 1 2 3 4 [ ] 6 7 8 9* Front list: 1 2 3 4* Back list: 9 8 7 6** User is expected to keep track of the "current element" and properly* fill it back in as necessary. (ToDo: Maybe it's more user friendly* to implicitly track the current element?)** Nota bene: the current class gets confused if you try to store NULLs* in the list.*/class HTMLPurifier_Zipper{public $front, $back;public function __construct($front, $back) {$this->front = $front;$this->back = $back;}/*** Creates a zipper from an array, with a hole in the* 0-index position.* @param Array to zipper-ify.* @return Tuple of zipper and element of first position.*/static public function fromArray($array) {$z = new self(array(), array_reverse($array));$t = $z->delete(); // delete the "dummy hole"return array($z, $t);}/*** Convert zipper back into a normal array, optionally filling in* the hole with a value. (Usually you should supply a $t, unless you* are at the end of the array.)*/public function toArray($t = NULL) {$a = $this->front;if ($t !== NULL) $a[] = $t;for ($i = count($this->back)-1; $i >= 0; $i--) {$a[] = $this->back[$i];}return $a;}/*** Move hole to the next element.* @param $t Element to fill hole with* @return Original contents of new hole.*/public function next($t) {if ($t !== NULL) array_push($this->front, $t);return empty($this->back) ? NULL : array_pop($this->back);}/*** Iterated hole advancement.* @param $t Element to fill hole with* @param $i How many forward to advance hole* @return Original contents of new hole, i away*/public function advance($t, $n) {for ($i = 0; $i < $n; $i++) {$t = $this->next($t);}return $t;}/*** Move hole to the previous element* @param $t Element to fill hole with* @return Original contents of new hole.*/public function prev($t) {if ($t !== NULL) array_push($this->back, $t);return empty($this->front) ? NULL : array_pop($this->front);}/*** Delete contents of current hole, shifting hole to* next element.* @return Original contents of new hole.*/public function delete() {return empty($this->back) ? NULL : array_pop($this->back);}/*** Returns true if we are at the end of the list.* @return bool*/public function done() {return empty($this->back);}/*** Insert element before hole.* @param Element to insert*/public function insertBefore($t) {if ($t !== NULL) array_push($this->front, $t);}/*** Insert element after hole.* @param Element to insert*/public function insertAfter($t) {if ($t !== NULL) array_push($this->back, $t);}/*** Splice in multiple elements at hole. Functional specification* in terms of array_splice:** $arr1 = $arr;* $old1 = array_splice($arr1, $i, $delete, $replacement);** list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);* $t = $z->advance($t, $i);* list($old2, $t) = $z->splice($t, $delete, $replacement);* $arr2 = $z->toArray($t);** assert($old1 === $old2);* assert($arr1 === $arr2);** NB: the absolute index location after this operation is* *unchanged!*** @param Current contents of hole.*/public function splice($t, $delete, $replacement) {// delete$old = array();$r = $t;for ($i = $delete; $i > 0; $i--) {$old[] = $r;$r = $this->delete();}// insertfor ($i = count($replacement)-1; $i >= 0; $i--) {$this->insertAfter($r);$r = $replacement[$i];}return array($old, $r);}}/*** 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 againif (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;}}/*** 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;}}// 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);}}/*** 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 signif ($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 numericif (!ctype_digit($digits)) {return false;}// perform scope testsif (!$this->zero && $integer == 0) {return false;}if (!$this->positive && $integer > 0) {return false;}if (!$this->negative && $integer < 0) {return false;}return $integer;}}/*** 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 checkreturn 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 upfor ($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;}}/*** 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);}}}/*** 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);}}/*** 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 stringreturn $uri->toString();}}/*** 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 allowedif (strpos($number, '.') === false) {return false;}list($left, $right) = explode('.', $number, 2);if ($left === '' && $right === '') {return false;}if ($left !== '' && !ctype_digit($left)) {return false;}$left = ltrim($left, '0');$right = rtrim($right, '0');if ($right === '') {return $left ? $sign . $left : '0';} elseif (!ctype_digit($right)) {return false;}return $sign . $left . '.' . $right;}}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;}}/*** 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'];}/*** @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;$i = 0; // number of catchesforeach ($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);}}/* 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 W3CInternet Explorer and Opera, however, support arbitrary ordering. Weshould 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 keywordif ($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);}}/*** 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 valueforeach ($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);}}/*** 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 channelif ($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 handlingif ($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;}}/*** 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;}}/*** 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);}}/*** 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;}}/*** 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 = ''; // outputfor ($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-weightforeach ($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 onif (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-heightif ($line_height === false) {// we need to scroll forwardfor ($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 successfullyreturn rtrim($final);}return false;}}return false;}}/*** 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-8for ($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 isO(length of string * length of mask), making it inefficientfor large masks. However, it's still faster thanpreg_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 nameif (isset($generic_names[$font])) {if ($allowed_fonts === null || isset($allowed_fonts[$font])) {$final .= $font . ', ';}continue;}// match a quoted nameif ($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 nameif ($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;}}/*** 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 invalidif (!$string) {return false;}$pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';if (!preg_match($pattern, $string)) {return false;}return $string;}}/*** 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 ! importantif (strlen($string) >= 9 && substr($string, -9) === 'important') {$temp = rtrim(substr($string, 0, -9));// use a temp, because we might want to restore importantif (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;}}/*** 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);// Optimizationsif ($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();}}/*** 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 bitif ($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 typeif ($caught['type']) {$ret[] = $caught['type'];}// construct imageif ($caught['image']) {$ret[] = $caught['image'];}// construct positionif ($caught['position']) {$ret[] = $caught['position'];}if (empty($ret)) {return false;}return implode(' ', $ret);}}/*** 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);}}/*** 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%";}}/*** 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;}}/*** 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\")";}}/*** 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);}}/*** 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 invalidif (!$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 endpreg_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;}}/*** 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;}}/*** 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";}}/*** 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);}}/*** 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 firstif ($id === '') {return false;}$prefix = $config->get('Attr.IDPrefix');if ($prefix !== '') {$prefix .= $config->get('Attr.IDPrefixLocal');// prevent re-appending the prefixif (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 fasterif ($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;}}/*** 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 Windowsif ($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);}}/*** 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) . '%';}}/*** 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;}}/*** 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) . '*';}}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/*** 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 nativelyif (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 againif (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {return $string;}return false;}}/*** 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})";}}/*** 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 checkif (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 checkif (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;}}/*** 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 doneif ($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;}}/*** 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;}}// 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;}}/*** 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;}}/*** 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;}}/*** 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;}}/*** 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;}}// 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;}}/*** 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 hereif (!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;}}/*** 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;}}/*** 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;}}/*** 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;}}/*** 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 nameif ($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;}}/*** 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{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;}}// 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;}}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;}}/*** 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;}}/*** 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;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.phpcase '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 pairsdefault:$attr['name'] = $attr['value'] = null;}return $attr;}}/*** 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;}}// 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;}}// 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;}}// 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;}}/*** 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 Firefoxif (!isset($attr['cols'])) {$attr['cols'] = '22';}if (!isset($attr['rows'])) {$attr['rows'] = '3';}return $attr;}}/*** 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);}}}/*** 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 arraypreg_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 nestforeach ($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;}}/*** 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();}}/*** 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 unsatisfactorypublic $elements = array('li' => true, 'ul' => true, 'ol' => true);/*** @param array $children* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return array*/public function validateChildren($children, $config, $context){// Flag for subclasses$this->whitespace = false;// if there are no tokens, delete parent nodeif (empty($children)) {return false;}// if li is not allowed, delete parent nodeif (!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 whitespaceif ($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;}}/*** 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 nodeif (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 whitespaceif (!isset($this->elements[$node->name])) {// special case text// XXX One of these ought to be redundant or somethingif ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {$result[] = $node;continue;}// spill the child contents in// ToDo: Make configurableif ($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;}}/*** 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 modifiedif ($result === false) {if (empty($children)) {return true;} elseif ($this->whitespace) {return $children;} else {return array();}}return $result;}}/*** 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;}}}/*** 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 throughcase '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)) {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;}}class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache{/*** Cache object we are decorating* @type HTMLPurifier_DefinitionCache*/public $cache;/*** The name of the decorator* @var string*/public $name;public function __construct(){}/*** Lazy decorator function* @param HTMLPurifier_DefinitionCache $cache Reference to cache object to decorate* @return HTMLPurifier_DefinitionCache_Decorator*/public function decorate(&$cache){$decorator = $this->copy();// reference is necessary for mocks in PHP 4$decorator->cache =& $cache;$decorator->type = $cache->type;return $decorator;}/*** Cross-compatible clone substitute* @return HTMLPurifier_DefinitionCache_Decorator*/public function copy(){return new HTMLPurifier_DefinitionCache_Decorator();}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function add($def, $config){return $this->cache->add($def, $config);}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function set($def, $config){return $this->cache->set($def, $config);}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function replace($def, $config){return $this->cache->replace($def, $config);}/*** @param HTMLPurifier_Config $config* @return mixed*/public function get($config){return $this->cache->get($config);}/*** @param HTMLPurifier_Config $config* @return mixed*/public function remove($config){return $this->cache->remove($config);}/*** @param HTMLPurifier_Config $config* @return mixed*/public function flush($config){return $this->cache->flush($config);}/*** @param HTMLPurifier_Config $config* @return mixed*/public function cleanup($config){return $this->cache->cleanup($config);}}/*** Null cache object to use when no caching is on.*/class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache{/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return bool*/public function add($def, $config){return false;}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return bool*/public function set($def, $config){return false;}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return bool*/public function replace($def, $config){return false;}/*** @param HTMLPurifier_Config $config* @return bool*/public function remove($config){return false;}/*** @param HTMLPurifier_Config $config* @return bool*/public function get($config){return false;}/*** @param HTMLPurifier_Config $config* @return bool*/public function flush($config){return false;}/*** @param HTMLPurifier_Config $config* @return bool*/public function cleanup($config){return false;}}class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache{/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return int|bool*/public function add($def, $config){if (!$this->checkDefType($def)) {return;}$file = $this->generateFilePath($config);if (file_exists($file)) {return false;}if (!$this->_prepareDir($config)) {return false;}return $this->_write($file, serialize($def), $config);}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return int|bool*/public function set($def, $config){if (!$this->checkDefType($def)) {return;}$file = $this->generateFilePath($config);if (!$this->_prepareDir($config)) {return false;}return $this->_write($file, serialize($def), $config);}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return int|bool*/public function replace($def, $config){if (!$this->checkDefType($def)) {return;}$file = $this->generateFilePath($config);if (!file_exists($file)) {return false;}if (!$this->_prepareDir($config)) {return false;}return $this->_write($file, serialize($def), $config);}/*** @param HTMLPurifier_Config $config* @return bool|HTMLPurifier_Config*/public function get($config){$file = $this->generateFilePath($config);if (!file_exists($file)) {return false;}return unserialize(file_get_contents($file));}/*** @param HTMLPurifier_Config $config* @return bool*/public function remove($config){$file = $this->generateFilePath($config);if (!file_exists($file)) {return false;}return unlink($file);}/*** @param HTMLPurifier_Config $config* @return bool*/public function flush($config){if (!$this->_prepareDir($config)) {return false;}$dir = $this->generateDirectoryPath($config);$dh = opendir($dir);// Apparently, on some versions of PHP, readdir will return// an empty string if you pass an invalid argument to readdir.// So you need this test. See #49.if (false === $dh) {return false;}while (false !== ($filename = readdir($dh))) {if (empty($filename)) {continue;}if ($filename[0] === '.') {continue;}unlink($dir . '/' . $filename);}closedir($dh);return true;}/*** @param HTMLPurifier_Config $config* @return bool*/public function cleanup($config){if (!$this->_prepareDir($config)) {return false;}$dir = $this->generateDirectoryPath($config);$dh = opendir($dir);// See #49 (and above).if (false === $dh) {return false;}while (false !== ($filename = readdir($dh))) {if (empty($filename)) {continue;}if ($filename[0] === '.') {continue;}$key = substr($filename, 0, strlen($filename) - 4);if ($this->isOld($key, $config)) {unlink($dir . '/' . $filename);}}closedir($dh);return true;}/*** Generates the file path to the serial file corresponding to* the configuration and definition name* @param HTMLPurifier_Config $config* @return string* @todo Make protected*/public function generateFilePath($config){$key = $this->generateKey($config);return $this->generateDirectoryPath($config) . '/' . $key . '.ser';}/*** Generates the path to the directory contain this cache's serial files* @param HTMLPurifier_Config $config* @return string* @note No trailing slash* @todo Make protected*/public function generateDirectoryPath($config){$base = $this->generateBaseDirectoryPath($config);return $base . '/' . $this->type;}/*** Generates path to base directory that contains all definition type* serials* @param HTMLPurifier_Config $config* @return mixed|string* @todo Make protected*/public function generateBaseDirectoryPath($config){$base = $config->get('Cache.SerializerPath');$base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;return $base;}/*** Convenience wrapper function for file_put_contents* @param string $file File name to write to* @param string $data Data to write into file* @param HTMLPurifier_Config $config* @return int|bool Number of bytes written if success, or false if failure.*/private function _write($file, $data, $config){$result = file_put_contents($file, $data);if ($result !== false) {// set permissions of the new file (no execute)$chmod = $config->get('Cache.SerializerPermissions');if ($chmod !== null) {chmod($file, $chmod & 0666);}}return $result;}/*** Prepares the directory that this type stores the serials in* @param HTMLPurifier_Config $config* @return bool True if successful*/private function _prepareDir($config){$directory = $this->generateDirectoryPath($config);$chmod = $config->get('Cache.SerializerPermissions');if ($chmod === null) {if (!@mkdir($directory) && !is_dir($directory)) {trigger_error('Could not create directory ' . $directory . '',E_USER_WARNING);return false;}return true;}if (!is_dir($directory)) {$base = $this->generateBaseDirectoryPath($config);if (!is_dir($base)) {trigger_error('Base directory ' . $base . ' does not exist,please create or change using %Cache.SerializerPath',E_USER_WARNING);return false;} elseif (!$this->_testPermissions($base, $chmod)) {return false;}if (!@mkdir($directory, $chmod) && !is_dir($directory)) {trigger_error('Could not create directory ' . $directory . '',E_USER_WARNING);return false;}if (!$this->_testPermissions($directory, $chmod)) {return false;}} elseif (!$this->_testPermissions($directory, $chmod)) {return false;}return true;}/*** Tests permissions on a directory and throws out friendly* error messages and attempts to chmod it itself if possible* @param string $dir Directory path* @param int $chmod Permissions* @return bool True if directory is writable*/private function _testPermissions($dir, $chmod){// early abort, if it is writable, everything is hunky-doryif (is_writable($dir)) {return true;}if (!is_dir($dir)) {// generally, you'll want to handle this beforehand// so a more specific error message can be giventrigger_error('Directory ' . $dir . ' does not exist',E_USER_WARNING);return false;}if (function_exists('posix_getuid') && $chmod !== null) {// POSIX system, we can give more specific adviceif (fileowner($dir) === posix_getuid()) {// we can chmod it ourselves$chmod = $chmod | 0700;if (chmod($dir, $chmod)) {return true;}} elseif (filegroup($dir) === posix_getgid()) {$chmod = $chmod | 0070;} else {// PHP's probably running as nobody, so we'll// need to give global permissions$chmod = $chmod | 0777;}trigger_error('Directory ' . $dir . ' not writable, ' .'please chmod to ' . decoct($chmod),E_USER_WARNING);} else {// generic error messagetrigger_error('Directory ' . $dir . ' not writable, ' .'please alter file permissions',E_USER_WARNING);}return false;}}/*** Definition cache decorator class that cleans up the cache* whenever there is a cache miss.*/class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends HTMLPurifier_DefinitionCache_Decorator{/*** @type string*/public $name = 'Cleanup';/*** @return HTMLPurifier_DefinitionCache_Decorator_Cleanup*/public function copy(){return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function add($def, $config){$status = parent::add($def, $config);if (!$status) {parent::cleanup($config);}return $status;}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function set($def, $config){$status = parent::set($def, $config);if (!$status) {parent::cleanup($config);}return $status;}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function replace($def, $config){$status = parent::replace($def, $config);if (!$status) {parent::cleanup($config);}return $status;}/*** @param HTMLPurifier_Config $config* @return mixed*/public function get($config){$ret = parent::get($config);if (!$ret) {parent::cleanup($config);}return $ret;}}/*** Definition cache decorator class that saves all cache retrievals* to PHP's memory; good for unit tests or circumstances where* there are lots of configuration objects floating around.*/class HTMLPurifier_DefinitionCache_Decorator_Memory extends HTMLPurifier_DefinitionCache_Decorator{/*** @type array*/protected $definitions;/*** @type string*/public $name = 'Memory';/*** @return HTMLPurifier_DefinitionCache_Decorator_Memory*/public function copy(){return new HTMLPurifier_DefinitionCache_Decorator_Memory();}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function add($def, $config){$status = parent::add($def, $config);if ($status) {$this->definitions[$this->generateKey($config)] = $def;}return $status;}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function set($def, $config){$status = parent::set($def, $config);if ($status) {$this->definitions[$this->generateKey($config)] = $def;}return $status;}/*** @param HTMLPurifier_Definition $def* @param HTMLPurifier_Config $config* @return mixed*/public function replace($def, $config){$status = parent::replace($def, $config);if ($status) {$this->definitions[$this->generateKey($config)] = $def;}return $status;}/*** @param HTMLPurifier_Config $config* @return mixed*/public function get($config){$key = $this->generateKey($config);if (isset($this->definitions[$key])) {return $this->definitions[$key];}$this->definitions[$key] = parent::get($config);return $this->definitions[$key];}}/*** XHTML 1.1 Bi-directional Text Module, defines elements that* declare directionality of content. Text Extension Module.*/class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Bdo';/*** @type array*/public $attr_collections = array('I18N' => array('dir' => false));/*** @param HTMLPurifier_Config $config*/public function setup($config){$bdo = $this->addElement('bdo','Inline','Inline',array('Core', 'Lang'),array('dir' => 'Enum#ltr,rtl', // required// The Abstract Module specification has the attribute// inclusions wrong for bdo: bdo allows Lang));$bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();$this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';}}class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'CommonAttributes';/*** @type array*/public $attr_collections = array('Core' => array(0 => array('Style'),// 'xml:space' => false,'class' => 'Class','id' => 'ID','title' => 'CDATA',),'Lang' => array(),'I18N' => array(0 => array('Lang'), // proprietary, for xml:lang/lang),'Common' => array(0 => array('Core', 'I18N')));}/*** XHTML 1.1 Edit Module, defines editing-related elements. Text Extension* Module.*/class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Edit';/*** @param HTMLPurifier_Config $config*/public function setup($config){$contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';$attr = array('cite' => 'URI',// 'datetime' => 'Datetime', // not implemented);$this->addElement('del', 'Inline', $contents, 'Common', $attr);$this->addElement('ins', 'Inline', $contents, 'Common', $attr);}// HTML 4.01 specifies that ins/del must not contain block// elements when used in an inline context, chameleon is// a complicated workaround to acheive this effect// Inline context ! Block context (exclamation mark is// separator, see getChildDef for parsing)/*** @type bool*/public $defines_child_def = true;/*** @param HTMLPurifier_ElementDef $def* @return HTMLPurifier_ChildDef_Chameleon*/public function getChildDef($def){if ($def->content_model_type != 'chameleon') {return false;}$value = explode('!', $def->content_model);return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);}}/*** XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.*/class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Forms';/*** @type bool*/public $safe = false;/*** @type array*/public $content_sets = array('Block' => 'Form','Inline' => 'Formctrl',);/*** @param HTMLPurifier_Config $config*/public function setup($config){$form = $this->addElement('form','Form','Required: Heading | List | Block | fieldset','Common',array('accept' => 'ContentTypes','accept-charset' => 'Charsets','action*' => 'URI','method' => 'Enum#get,post',// really ContentType, but these two are the only ones used today'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',));$form->excludes = array('form' => true);$input = $this->addElement('input','Formctrl','Empty','Common',array('accept' => 'ContentTypes','accesskey' => 'Character','alt' => 'Text','checked' => 'Bool#checked','disabled' => 'Bool#disabled','maxlength' => 'Number','name' => 'CDATA','readonly' => 'Bool#readonly','size' => 'Number','src' => 'URI#embedded','tabindex' => 'Number','type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image','value' => 'CDATA',));$input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();$this->addElement('select','Formctrl','Required: optgroup | option','Common',array('disabled' => 'Bool#disabled','multiple' => 'Bool#multiple','name' => 'CDATA','size' => 'Number','tabindex' => 'Number',));$this->addElement('option',false,'Optional: #PCDATA','Common',array('disabled' => 'Bool#disabled','label' => 'Text','selected' => 'Bool#selected','value' => 'CDATA',));// It's illegal for there to be more than one selected, but not// be multiple. Also, no selected means undefined behavior. This might// be difficult to implement; perhaps an injector, or a context variable.$textarea = $this->addElement('textarea','Formctrl','Optional: #PCDATA','Common',array('accesskey' => 'Character','cols*' => 'Number','disabled' => 'Bool#disabled','name' => 'CDATA','readonly' => 'Bool#readonly','rows*' => 'Number','tabindex' => 'Number',));$textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();$button = $this->addElement('button','Formctrl','Optional: #PCDATA | Heading | List | Block | Inline','Common',array('accesskey' => 'Character','disabled' => 'Bool#disabled','name' => 'CDATA','tabindex' => 'Number','type' => 'Enum#button,submit,reset','value' => 'CDATA',));// For exclusions, ideally we'd specify content sets, not literal elements$button->excludes = $this->makeLookup('form','fieldset', // Form'input','select','textarea','label','button', // Formctrl'a', // as per HTML 4.01 spec, this is omitted by modularization'isindex','iframe' // legacy items);// Extra exclusion: img usemap="" is not permitted within this element.// We'll omit this for now, since we don't have any good way of// indicating it yet.// This is HIGHLY user-unfriendly; we need a custom child-def for this$this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');$label = $this->addElement('label','Formctrl','Optional: #PCDATA | Inline','Common',array('accesskey' => 'Character',// 'for' => 'IDREF', // IDREF not implemented, cannot allow));$label->excludes = array('label' => true);$this->addElement('legend',false,'Optional: #PCDATA | Inline','Common',array('accesskey' => 'Character',));$this->addElement('optgroup',false,'Required: option','Common',array('disabled' => 'Bool#disabled','label*' => 'Text',));// Don't forget an injector for <isindex>. This one's a little complex// because it maps to multiple elements.}}/*** XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.*/class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Hypertext';/*** @param HTMLPurifier_Config $config*/public function setup($config){$a = $this->addElement('a','Inline','Inline','Common',array(// 'accesskey' => 'Character',// 'charset' => 'Charset','href' => 'URI',// 'hreflang' => 'LanguageCode','rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),// 'tabindex' => 'Number',// 'type' => 'ContentType',));$a->formatting = true;$a->excludes = array('a' => true);}}/*** XHTML 1.1 Iframe Module provides inline frames.** @note This module is not considered safe unless an Iframe* whitelisting mechanism is specified. Currently, the only* such mechanism is %URL.SafeIframeRegexp*/class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Iframe';/*** @type bool*/public $safe = false;/*** @param HTMLPurifier_Config $config*/public function setup($config){if ($config->get('HTML.SafeIframe')) {$this->safe = true;}$this->addElement('iframe','Inline','Flow','Common',array('src' => 'URI#embedded','width' => 'Length','height' => 'Length','name' => 'ID','scrolling' => 'Enum#yes,no,auto','frameborder' => 'Enum#0,1','longdesc' => 'URI','marginheight' => 'Pixels','marginwidth' => 'Pixels',));}}/*** XHTML 1.1 Image Module provides basic image embedding.* @note There is specialized code for removing empty images in* HTMLPurifier_Strategy_RemoveForeignElements*/class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Image';/*** @param HTMLPurifier_Config $config*/public function setup($config){$max = $config->get('HTML.MaxImgLength');$img = $this->addElement('img','Inline','Empty','Common',array('alt*' => 'Text',// According to the spec, it's Length, but percents can// be abused, so we allow only Pixels.'height' => 'Pixels#' . $max,'width' => 'Pixels#' . $max,'longdesc' => 'URI','src*' => new HTMLPurifier_AttrDef_URI(true), // embedded));if ($max === null || $config->get('HTML.Trusted')) {$img->attr['height'] =$img->attr['width'] = 'Length';}// kind of strange, but splitting things up would be inefficient$img->attr_transform_pre[] =$img->attr_transform_post[] =new HTMLPurifier_AttrTransform_ImgRequired();}}/*** XHTML 1.1 Legacy module defines elements that were previously* deprecated.** @note Not all legacy elements have been implemented yet, which* is a bit of a reverse problem as compared to browsers! In* addition, this legacy module may implement a bit more than* mandated by XHTML 1.1.** This module can be used in combination with TransformToStrict in order* to transform as many deprecated elements as possible, but retain* questionably deprecated elements that do not have good alternatives* as well as transform elements that don't have an implementation.* See docs/ref-strictness.txt for more details.*/class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Legacy';/*** @param HTMLPurifier_Config $config*/public function setup($config){$this->addElement('basefont','Inline','Empty',null,array('color' => 'Color','face' => 'Text', // extremely broad, we should'size' => 'Text', // tighten it'id' => 'ID'));$this->addElement('center', 'Block', 'Flow', 'Common');$this->addElement('dir','Block','Required: li','Common',array('compact' => 'Bool#compact'));$this->addElement('font','Inline','Inline',array('Core', 'I18N'),array('color' => 'Color','face' => 'Text', // extremely broad, we should'size' => 'Text', // tighten it));$this->addElement('menu','Block','Required: li','Common',array('compact' => 'Bool#compact'));$s = $this->addElement('s', 'Inline', 'Inline', 'Common');$s->formatting = true;$strike = $this->addElement('strike', 'Inline', 'Inline', 'Common');$strike->formatting = true;$u = $this->addElement('u', 'Inline', 'Inline', 'Common');$u->formatting = true;// setup modifications to old elements$align = 'Enum#left,right,center,justify';$address = $this->addBlankElement('address');$address->content_model = 'Inline | #PCDATA | p';$address->content_model_type = 'optional';$address->child = false;$blockquote = $this->addBlankElement('blockquote');$blockquote->content_model = 'Flow | #PCDATA';$blockquote->content_model_type = 'optional';$blockquote->child = false;$br = $this->addBlankElement('br');$br->attr['clear'] = 'Enum#left,all,right,none';$caption = $this->addBlankElement('caption');$caption->attr['align'] = 'Enum#top,bottom,left,right';$div = $this->addBlankElement('div');$div->attr['align'] = $align;$dl = $this->addBlankElement('dl');$dl->attr['compact'] = 'Bool#compact';for ($i = 1; $i <= 6; $i++) {$h = $this->addBlankElement("h$i");$h->attr['align'] = $align;}$hr = $this->addBlankElement('hr');$hr->attr['align'] = $align;$hr->attr['noshade'] = 'Bool#noshade';$hr->attr['size'] = 'Pixels';$hr->attr['width'] = 'Length';$img = $this->addBlankElement('img');$img->attr['align'] = 'IAlign';$img->attr['border'] = 'Pixels';$img->attr['hspace'] = 'Pixels';$img->attr['vspace'] = 'Pixels';// figure out this integer business$li = $this->addBlankElement('li');$li->attr['value'] = new HTMLPurifier_AttrDef_Integer();$li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';$ol = $this->addBlankElement('ol');$ol->attr['compact'] = 'Bool#compact';$ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();$ol->attr['type'] = 'Enum#s:1,i,I,a,A';$p = $this->addBlankElement('p');$p->attr['align'] = $align;$pre = $this->addBlankElement('pre');$pre->attr['width'] = 'Number';// script omitted$table = $this->addBlankElement('table');$table->attr['align'] = 'Enum#left,center,right';$table->attr['bgcolor'] = 'Color';$tr = $this->addBlankElement('tr');$tr->attr['bgcolor'] = 'Color';$th = $this->addBlankElement('th');$th->attr['bgcolor'] = 'Color';$th->attr['height'] = 'Length';$th->attr['nowrap'] = 'Bool#nowrap';$th->attr['width'] = 'Length';$td = $this->addBlankElement('td');$td->attr['bgcolor'] = 'Color';$td->attr['height'] = 'Length';$td->attr['nowrap'] = 'Bool#nowrap';$td->attr['width'] = 'Length';$ul = $this->addBlankElement('ul');$ul->attr['compact'] = 'Bool#compact';$ul->attr['type'] = 'Enum#square,disc,circle';// "safe" modifications to "unsafe" elements// WARNING: If you want to add support for an unsafe, legacy// attribute, make a new TrustedLegacy module with the trusted// bit set appropriately$form = $this->addBlankElement('form');$form->content_model = 'Flow | #PCDATA';$form->content_model_type = 'optional';$form->attr['target'] = 'FrameTarget';$input = $this->addBlankElement('input');$input->attr['align'] = 'IAlign';$legend = $this->addBlankElement('legend');$legend->attr['align'] = 'LAlign';}}/*** XHTML 1.1 List Module, defines list-oriented elements. Core Module.*/class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'List';// According to the abstract schema, the List content set is a fully formed// one or more expr, but it invariably occurs in an optional declaration// so we're not going to do that subtlety. It might cause trouble// if a user defines "List" and expects that multiple lists are// allowed to be specified, but then again, that's not very intuitive.// Furthermore, the actual XML Schema may disagree. Regardless,// we don't have support for such nested expressions without using// the incredibly inefficient and draconic Custom ChildDef./*** @type array*/public $content_sets = array('Flow' => 'List');/*** @param HTMLPurifier_Config $config*/public function setup($config){$ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common');$ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common');// XXX The wrap attribute is handled by MakeWellFormed. This is all// quite unsatisfactory, because we generated this// *specifically* for lists, and now a big chunk of the handling// is done properly by the List ChildDef. So actually, we just// want enough information to make autoclosing work properly,// and then hand off the tricky stuff to the ChildDef.$ol->wrap = 'li';$ul->wrap = 'li';$this->addElement('dl', 'List', 'Required: dt | dd', 'Common');$this->addElement('li', false, 'Flow', 'Common');$this->addElement('dd', false, 'Flow', 'Common');$this->addElement('dt', false, 'Inline', 'Common');}}class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Name';/*** @param HTMLPurifier_Config $config*/public function setup($config){$elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map');foreach ($elements as $name) {$element = $this->addBlankElement($name);$element->attr['name'] = 'CDATA';if (!$config->get('HTML.Attr.Name.UseCDATA')) {$element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync();}}}}/*** Module adds the nofollow attribute transformation to a tags. It* is enabled by HTML.Nofollow*/class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Nofollow';/*** @param HTMLPurifier_Config $config*/public function setup($config){$a = $this->addBlankElement('a');$a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow();}}class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'NonXMLCommonAttributes';/*** @type array*/public $attr_collections = array('Lang' => array('lang' => 'LanguageCode',));}/*** XHTML 1.1 Object Module, defines elements for generic object inclusion* @warning Users will commonly use <embed> to cater to legacy browsers: this* module does not allow this sort of behavior*/class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Object';/*** @type bool*/public $safe = false;/*** @param HTMLPurifier_Config $config*/public function setup($config){$this->addElement('object','Inline','Optional: #PCDATA | Flow | param','Common',array('archive' => 'URI','classid' => 'URI','codebase' => 'URI','codetype' => 'Text','data' => 'URI','declare' => 'Bool#declare','height' => 'Length','name' => 'CDATA','standby' => 'Text','tabindex' => 'Number','type' => 'ContentType','width' => 'Length'));$this->addElement('param',false,'Empty',null,array('id' => 'ID','name*' => 'Text','type' => 'Text','value' => 'Text','valuetype' => 'Enum#data,ref,object'));}}/*** XHTML 1.1 Presentation Module, defines simple presentation-related* markup. Text Extension Module.* @note The official XML Schema and DTD specs further divide this into* two modules:* - Block Presentation (hr)* - Inline Presentation (b, big, i, small, sub, sup, tt)* We have chosen not to heed this distinction, as content_sets* provides satisfactory disambiguation.*/class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Presentation';/*** @param HTMLPurifier_Config $config*/public function setup($config){$this->addElement('hr', 'Block', 'Empty', 'Common');$this->addElement('sub', 'Inline', 'Inline', 'Common');$this->addElement('sup', 'Inline', 'Inline', 'Common');$b = $this->addElement('b', 'Inline', 'Inline', 'Common');$b->formatting = true;$big = $this->addElement('big', 'Inline', 'Inline', 'Common');$big->formatting = true;$i = $this->addElement('i', 'Inline', 'Inline', 'Common');$i->formatting = true;$small = $this->addElement('small', 'Inline', 'Inline', 'Common');$small->formatting = true;$tt = $this->addElement('tt', 'Inline', 'Inline', 'Common');$tt->formatting = true;}}/*** Module defines proprietary tags and attributes in HTML.* @warning If this module is enabled, standards-compliance is off!*/class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Proprietary';/*** @param HTMLPurifier_Config $config*/public function setup($config){$this->addElement('marquee','Inline','Flow','Common',array('direction' => 'Enum#left,right,up,down','behavior' => 'Enum#alternate','width' => 'Length','height' => 'Length','scrolldelay' => 'Number','scrollamount' => 'Number','loop' => 'Number','bgcolor' => 'Color','hspace' => 'Pixels','vspace' => 'Pixels',));}}/*** XHTML 1.1 Ruby Annotation Module, defines elements that indicate* short runs of text alongside base text for annotation or pronounciation.*/class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Ruby';/*** @param HTMLPurifier_Config $config*/public function setup($config){$this->addElement('ruby','Inline','Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))','Common');$this->addElement('rbc', false, 'Required: rb', 'Common');$this->addElement('rtc', false, 'Required: rt', 'Common');$rb = $this->addElement('rb', false, 'Inline', 'Common');$rb->excludes = array('ruby' => true);$rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));$rt->excludes = array('ruby' => true);$this->addElement('rp', false, 'Optional: #PCDATA', 'Common');}}/*** A "safe" embed module. See SafeObject. This is a proprietary element.*/class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'SafeEmbed';/*** @param HTMLPurifier_Config $config*/public function setup($config){$max = $config->get('HTML.MaxImgLength');$embed = $this->addElement('embed','Inline','Empty','Common',array('src*' => 'URI#embedded','type' => 'Enum#application/x-shockwave-flash','width' => 'Pixels#' . $max,'height' => 'Pixels#' . $max,'allowscriptaccess' => 'Enum#never','allownetworking' => 'Enum#internal','flashvars' => 'Text','wmode' => 'Enum#window,transparent,opaque','name' => 'ID',));$embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();}}/*** A "safe" object module. In theory, objects permitted by this module will* be safe, and untrusted users can be allowed to embed arbitrary flash objects* (maybe other types too, but only Flash is supported as of right now).* Highly experimental.*/class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'SafeObject';/*** @param HTMLPurifier_Config $config*/public function setup($config){// These definitions are not intrinsically safe: the attribute transforms// are a vital part of ensuring safety.$max = $config->get('HTML.MaxImgLength');$object = $this->addElement('object','Inline','Optional: param | Flow | #PCDATA','Common',array(// While technically not required by the spec, we're forcing// it to this value.'type' => 'Enum#application/x-shockwave-flash','width' => 'Pixels#' . $max,'height' => 'Pixels#' . $max,'data' => 'URI#embedded','codebase' => new HTMLPurifier_AttrDef_Enum(array('http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0')),));$object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();$param = $this->addElement('param',false,'Empty',false,array('id' => 'ID','name*' => 'Text','value' => 'Text'));$param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();$this->info_injector[] = 'SafeObject';}}/*** A "safe" script module. No inline JS is allowed, and pointed to JS* files must match whitelist.*/class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'SafeScripting';/*** @param HTMLPurifier_Config $config*/public function setup($config){// These definitions are not intrinsically safe: the attribute transforms// are a vital part of ensuring safety.$allowed = $config->get('HTML.SafeScripting');$script = $this->addElement('script','Inline','Optional:', // Not `Empty` to not allow to autoclose the <script /> tag @see https://www.w3.org/TR/html4/interact/scripts.htmlnull,array(// While technically not required by the spec, we're forcing// it to this value.'type' => 'Enum#text/javascript','src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed), /*case sensitive*/ true)));$script->attr_transform_pre[] =$script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();}}/*WARNING: THIS MODULE IS EXTREMELY DANGEROUS AS IT ENABLES INLINE SCRIPTINGINSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!!*//*** XHTML 1.1 Scripting module, defines elements that are used to contain* information pertaining to executable scripts or the lack of support* for executable scripts.* @note This module does not contain inline scripting elements*/class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Scripting';/*** @type array*/public $elements = array('script', 'noscript');/*** @type array*/public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');/*** @type bool*/public $safe = false;/*** @param HTMLPurifier_Config $config*/public function setup($config){// TODO: create custom child-definition for noscript that// auto-wraps stray #PCDATA in a similar manner to// blockquote's custom definition (we would use it but// blockquote's contents are optional while noscript's contents// are required)// TODO: convert this to new syntax, main problem is getting// both content sets working// In theory, this could be safe, but I don't see any reason to// allow it.$this->info['noscript'] = new HTMLPurifier_ElementDef();$this->info['noscript']->attr = array(0 => array('Common'));$this->info['noscript']->content_model = 'Heading | List | Block';$this->info['noscript']->content_model_type = 'required';$this->info['script'] = new HTMLPurifier_ElementDef();$this->info['script']->attr = array('defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),'src' => new HTMLPurifier_AttrDef_URI(true),'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript')));$this->info['script']->content_model = '#PCDATA';$this->info['script']->content_model_type = 'optional';$this->info['script']->attr_transform_pre[] =$this->info['script']->attr_transform_post[] =new HTMLPurifier_AttrTransform_ScriptRequired();}}/*** XHTML 1.1 Edit Module, defines editing-related elements. Text Extension* Module.*/class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'StyleAttribute';/*** @type array*/public $attr_collections = array(// The inclusion routine differs from the Abstract Modules but// is in line with the DTD and XML Schemas.'Style' => array('style' => false), // see constructor'Core' => array(0 => array('Style')));/*** @param HTMLPurifier_Config $config*/public function setup($config){$this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();}}/*** XHTML 1.1 Tables Module, fully defines accessible table elements.*/class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Tables';/*** @param HTMLPurifier_Config $config*/public function setup($config){$this->addElement('caption', false, 'Inline', 'Common');$this->addElement('table','Block',new HTMLPurifier_ChildDef_Table(),'Common',array('border' => 'Pixels','cellpadding' => 'Length','cellspacing' => 'Length','frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border','rules' => 'Enum#none,groups,rows,cols,all','summary' => 'Text','width' => 'Length'));// common attributes$cell_align = array('align' => 'Enum#left,center,right,justify,char','charoff' => 'Length','valign' => 'Enum#top,middle,bottom,baseline',);$cell_t = array_merge(array('abbr' => 'Text','colspan' => 'Number','rowspan' => 'Number',// Apparently, as of HTML5 this attribute only applies// to 'th' elements.'scope' => 'Enum#row,col,rowgroup,colgroup',),$cell_align);$this->addElement('td', false, 'Flow', 'Common', $cell_t);$this->addElement('th', false, 'Flow', 'Common', $cell_t);$this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);$cell_col = array_merge(array('span' => 'Number','width' => 'MultiLength',),$cell_align);$this->addElement('col', false, 'Empty', 'Common', $cell_col);$this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);$this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);$this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);$this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);}}/*** XHTML 1.1 Target Module, defines target attribute in link elements.*/class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Target';/*** @param HTMLPurifier_Config $config*/public function setup($config){$elements = array('a');foreach ($elements as $name) {$e = $this->addBlankElement($name);$e->attr = array('target' => new HTMLPurifier_AttrDef_HTML_FrameTarget());}}}/*** Module adds the target=blank attribute transformation to a tags. It* is enabled by HTML.TargetBlank*/class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'TargetBlank';/*** @param HTMLPurifier_Config $config*/public function setup($config){$a = $this->addBlankElement('a');$a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank();}}/*** Module adds the target-based noopener attribute transformation to a tags. It* is enabled by HTML.TargetNoopener*/class HTMLPurifier_HTMLModule_TargetNoopener extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'TargetNoopener';/*** @param HTMLPurifier_Config $config*/public function setup($config) {$a = $this->addBlankElement('a');$a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoopener();}}/*** Module adds the target-based noreferrer attribute transformation to a tags. It* is enabled by HTML.TargetNoreferrer*/class HTMLPurifier_HTMLModule_TargetNoreferrer extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'TargetNoreferrer';/*** @param HTMLPurifier_Config $config*/public function setup($config) {$a = $this->addBlankElement('a');$a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoreferrer();}}/*** XHTML 1.1 Text Module, defines basic text containers. Core Module.* @note In the normative XML Schema specification, this module* is further abstracted into the following modules:* - Block Phrasal (address, blockquote, pre, h1, h2, h3, h4, h5, h6)* - Block Structural (div, p)* - Inline Phrasal (abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var)* - Inline Structural (br, span)* This module, functionally, does not distinguish between these* sub-modules, but the code is internally structured to reflect* these distinctions.*/class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'Text';/*** @type array*/public $content_sets = array('Flow' => 'Heading | Block | Inline');/*** @param HTMLPurifier_Config $config*/public function setup($config){// Inline Phrasal -------------------------------------------------$this->addElement('abbr', 'Inline', 'Inline', 'Common');$this->addElement('acronym', 'Inline', 'Inline', 'Common');$this->addElement('cite', 'Inline', 'Inline', 'Common');$this->addElement('dfn', 'Inline', 'Inline', 'Common');$this->addElement('kbd', 'Inline', 'Inline', 'Common');$this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI'));$this->addElement('samp', 'Inline', 'Inline', 'Common');$this->addElement('var', 'Inline', 'Inline', 'Common');$em = $this->addElement('em', 'Inline', 'Inline', 'Common');$em->formatting = true;$strong = $this->addElement('strong', 'Inline', 'Inline', 'Common');$strong->formatting = true;$code = $this->addElement('code', 'Inline', 'Inline', 'Common');$code->formatting = true;// Inline Structural ----------------------------------------------$this->addElement('span', 'Inline', 'Inline', 'Common');$this->addElement('br', 'Inline', 'Empty', 'Core');// Block Phrasal --------------------------------------------------$this->addElement('address', 'Block', 'Inline', 'Common');$this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI'));$pre = $this->addElement('pre', 'Block', 'Inline', 'Common');$pre->excludes = $this->makeLookup('img','big','small','object','applet','font','basefont');$this->addElement('h1', 'Heading', 'Inline', 'Common');$this->addElement('h2', 'Heading', 'Inline', 'Common');$this->addElement('h3', 'Heading', 'Inline', 'Common');$this->addElement('h4', 'Heading', 'Inline', 'Common');$this->addElement('h5', 'Heading', 'Inline', 'Common');$this->addElement('h6', 'Heading', 'Inline', 'Common');// Block Structural -----------------------------------------------$p = $this->addElement('p', 'Block', 'Inline', 'Common');$p->autoclose = array_flip(array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul"));$this->addElement('div', 'Block', 'Flow', 'Common');}}/*** Abstract class for a set of proprietary modules that clean up (tidy)* poorly written HTML.* @todo Figure out how to protect some of these methods/properties*/class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule{/*** List of supported levels.* Index zero is a special case "no fixes" level.* @type array*/public $levels = array(0 => 'none', 'light', 'medium', 'heavy');/*** Default level to place all fixes in.* Disabled by default.* @type string*/public $defaultLevel = null;/*** Lists of fixes used by getFixesForLevel().* Format is:* HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');* @type array*/public $fixesForLevel = array('light' => array(),'medium' => array(),'heavy' => array());/*** Lazy load constructs the module by determining the necessary* fixes to create and then delegating to the populate() function.* @param HTMLPurifier_Config $config* @todo Wildcard matching and error reporting when an added or* subtracted fix has no effect.*/public function setup($config){// create fixes, initialize fixesForLevel$fixes = $this->makeFixes();$this->makeFixesForLevel($fixes);// figure out which fixes to use$level = $config->get('HTML.TidyLevel');$fixes_lookup = $this->getFixesForLevel($level);// get custom fix declarations: these need namespace processing$add_fixes = $config->get('HTML.TidyAdd');$remove_fixes = $config->get('HTML.TidyRemove');foreach ($fixes as $name => $fix) {// needs to be refactored a little to implement globbingif (isset($remove_fixes[$name]) ||(!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {unset($fixes[$name]);}}// populate this module with necessary fixes$this->populate($fixes);}/*** Retrieves all fixes per a level, returning fixes for that specific* level as well as all levels below it.* @param string $level level identifier, see $levels for valid values* @return array Lookup up table of fixes*/public function getFixesForLevel($level){if ($level == $this->levels[0]) {return array();}$activated_levels = array();for ($i = 1, $c = count($this->levels); $i < $c; $i++) {$activated_levels[] = $this->levels[$i];if ($this->levels[$i] == $level) {break;}}if ($i == $c) {trigger_error('Tidy level ' . htmlspecialchars($level) . ' not recognized',E_USER_WARNING);return array();}$ret = array();foreach ($activated_levels as $level) {foreach ($this->fixesForLevel[$level] as $fix) {$ret[$fix] = true;}}return $ret;}/*** Dynamically populates the $fixesForLevel member variable using* the fixes array. It may be custom overloaded, used in conjunction* with $defaultLevel, or not used at all.* @param array $fixes*/public function makeFixesForLevel($fixes){if (!isset($this->defaultLevel)) {return;}if (!isset($this->fixesForLevel[$this->defaultLevel])) {trigger_error('Default level ' . $this->defaultLevel . ' does not exist',E_USER_ERROR);return;}$this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);}/*** Populates the module with transforms and other special-case code* based on a list of fixes passed to it* @param array $fixes Lookup table of fixes to activate*/public function populate($fixes){foreach ($fixes as $name => $fix) {// determine what the fix is forlist($type, $params) = $this->getFixType($name);switch ($type) {case 'attr_transform_pre':case 'attr_transform_post':$attr = $params['attr'];if (isset($params['element'])) {$element = $params['element'];if (empty($this->info[$element])) {$e = $this->addBlankElement($element);} else {$e = $this->info[$element];}} else {$type = "info_$type";$e = $this;}// PHP does some weird parsing when I do// $e->$type[$attr], so I have to assign a ref.$f =& $e->$type;$f[$attr] = $fix;break;case 'tag_transform':$this->info_tag_transform[$params['element']] = $fix;break;case 'child':case 'content_model_type':$element = $params['element'];if (empty($this->info[$element])) {$e = $this->addBlankElement($element);} else {$e = $this->info[$element];}$e->$type = $fix;break;default:trigger_error("Fix type $type not supported", E_USER_ERROR);break;}}}/*** Parses a fix name and determines what kind of fix it is, as well* as other information defined by the fix* @param $name String name of fix* @return array(string $fix_type, array $fix_parameters)* @note $fix_parameters is type dependant, see populate() for usage* of these parameters*/public function getFixType($name){// parse it$property = $attr = null;if (strpos($name, '#') !== false) {list($name, $property) = explode('#', $name);}if (strpos($name, '@') !== false) {list($name, $attr) = explode('@', $name);}// figure out the parameters$params = array();if ($name !== '') {$params['element'] = $name;}if (!is_null($attr)) {$params['attr'] = $attr;}// special case: attribute transformif (!is_null($attr)) {if (is_null($property)) {$property = 'pre';}$type = 'attr_transform_' . $property;return array($type, $params);}// special case: tag transformif (is_null($property)) {return array('tag_transform', $params);}return array($property, $params);}/*** Defines all fixes the module will perform in a compact* associative array of fix name to fix implementation.* @return array*/public function makeFixes(){}}class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule{/*** @type string*/public $name = 'XMLCommonAttributes';/*** @type array*/public $attr_collections = array('Lang' => array('xml:lang' => 'LanguageCode',));}/*** Name is deprecated, but allowed in strict doctypes, so onl*/class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy{/*** @type string*/public $name = 'Tidy_Name';/*** @type string*/public $defaultLevel = 'heavy';/*** @return array*/public function makeFixes(){$r = array();// @name for img, a -----------------------------------------------// Technically, it's allowed even on strict, so we allow authors to use// it. However, it's deprecated in future versions of XHTML.$r['img@name'] =$r['a@name'] = new HTMLPurifier_AttrTransform_Name();return $r;}}class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy{/*** @type string*/public $name = 'Tidy_Proprietary';/*** @type string*/public $defaultLevel = 'light';/*** @return array*/public function makeFixes(){$r = array();$r['table@background'] = new HTMLPurifier_AttrTransform_Background();$r['td@background'] = new HTMLPurifier_AttrTransform_Background();$r['th@background'] = new HTMLPurifier_AttrTransform_Background();$r['tr@background'] = new HTMLPurifier_AttrTransform_Background();$r['thead@background'] = new HTMLPurifier_AttrTransform_Background();$r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();$r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();$r['table@height'] = new HTMLPurifier_AttrTransform_Length('height');return $r;}}class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy{/*** @return array*/public function makeFixes(){$r = array();// == deprecated tag transforms ===================================$r['font'] = new HTMLPurifier_TagTransform_Font();$r['menu'] = new HTMLPurifier_TagTransform_Simple('ul');$r['dir'] = new HTMLPurifier_TagTransform_Simple('ul');$r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');$r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');$r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');$r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');// == deprecated attribute transforms =============================$r['caption@align'] =new HTMLPurifier_AttrTransform_EnumToCSS('align',array(// we're following IE's behavior, not Firefox's, due// to the fact that no one supports caption-side:right,// W3C included (with CSS 2.1). This is a slightly// unreasonable attribute!'left' => 'text-align:left;','right' => 'text-align:right;','top' => 'caption-side:top;','bottom' => 'caption-side:bottom;' // not supported by IE));// @align for img -------------------------------------------------$r['img@align'] =new HTMLPurifier_AttrTransform_EnumToCSS('align',array('left' => 'float:left;','right' => 'float:right;','top' => 'vertical-align:top;','middle' => 'vertical-align:middle;','bottom' => 'vertical-align:baseline;',));// @align for table -----------------------------------------------$r['table@align'] =new HTMLPurifier_AttrTransform_EnumToCSS('align',array('left' => 'float:left;','center' => 'margin-left:auto;margin-right:auto;','right' => 'float:right;'));// @align for hr -----------------------------------------------$r['hr@align'] =new HTMLPurifier_AttrTransform_EnumToCSS('align',array(// we use both text-align and margin because these work// for different browsers (IE and Firefox, respectively)// and the melange makes for a pretty cross-compatible// solution'left' => 'margin-left:0;margin-right:auto;text-align:left;','center' => 'margin-left:auto;margin-right:auto;text-align:center;','right' => 'margin-left:auto;margin-right:0;text-align:right;'));// @align for h1, h2, h3, h4, h5, h6, p, div ----------------------// {{{$align_lookup = array();$align_values = array('left', 'right', 'center', 'justify');foreach ($align_values as $v) {$align_lookup[$v] = "text-align:$v;";}// }}}$r['h1@align'] =$r['h2@align'] =$r['h3@align'] =$r['h4@align'] =$r['h5@align'] =$r['h6@align'] =$r['p@align'] =$r['div@align'] =new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);// @bgcolor for table, tr, td, th ---------------------------------$r['table@bgcolor'] =$r['td@bgcolor'] =$r['th@bgcolor'] =new HTMLPurifier_AttrTransform_BgColor();// @border for img ------------------------------------------------$r['img@border'] = new HTMLPurifier_AttrTransform_Border();// @clear for br --------------------------------------------------$r['br@clear'] =new HTMLPurifier_AttrTransform_EnumToCSS('clear',array('left' => 'clear:left;','right' => 'clear:right;','all' => 'clear:both;','none' => 'clear:none;',));// @height for td, th ---------------------------------------------$r['td@height'] =$r['th@height'] =new HTMLPurifier_AttrTransform_Length('height');// @hspace for img ------------------------------------------------$r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');// @noshade for hr ------------------------------------------------// this transformation is not precise but often good enough.// different browsers use different styles to designate noshade$r['hr@noshade'] =new HTMLPurifier_AttrTransform_BoolToCSS('noshade','color:#808080;background-color:#808080;border:0;');// @nowrap for td, th ---------------------------------------------$r['td@nowrap'] =$r['th@nowrap'] =new HTMLPurifier_AttrTransform_BoolToCSS('nowrap','white-space:nowrap;');// @size for hr --------------------------------------------------$r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');// @type for li, ol, ul -------------------------------------------// {{{$ul_types = array('disc' => 'list-style-type:disc;','square' => 'list-style-type:square;','circle' => 'list-style-type:circle;');$ol_types = array('1' => 'list-style-type:decimal;','i' => 'list-style-type:lower-roman;','I' => 'list-style-type:upper-roman;','a' => 'list-style-type:lower-alpha;','A' => 'list-style-type:upper-alpha;');$li_types = $ul_types + $ol_types;// }}}$r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);$r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);$r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);// @vspace for img ------------------------------------------------$r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');// @width for hr, td, th ------------------------------------------$r['td@width'] =$r['th@width'] =$r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');return $r;}}class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4{/*** @type string*/public $name = 'Tidy_Strict';/*** @type string*/public $defaultLevel = 'light';/*** @return array*/public function makeFixes(){$r = parent::makeFixes();$r['blockquote#content_model_type'] = 'strictblockquote';return $r;}/*** @type bool*/public $defines_child_def = true;/*** @param HTMLPurifier_ElementDef $def* @return HTMLPurifier_ChildDef_StrictBlockquote*/public function getChildDef($def){if ($def->content_model_type != 'strictblockquote') {return parent::getChildDef($def);}return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);}}class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4{/*** @type string*/public $name = 'Tidy_Transitional';/*** @type string*/public $defaultLevel = 'heavy';}class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy{/*** @type string*/public $name = 'Tidy_XHTML';/*** @type string*/public $defaultLevel = 'medium';/*** @return array*/public function makeFixes(){$r = array();$r['@lang'] = new HTMLPurifier_AttrTransform_Lang();return $r;}}/*** Injector that auto paragraphs text in the root node based on* double-spacing.* @todo Ensure all states are unit tested, including variations as well.* @todo Make a graph of the flow control for this Injector.*/class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector{/*** @type string*/public $name = 'AutoParagraph';/*** @type array*/public $needed = array('p');/*** @return HTMLPurifier_Token_Start*/private function _pStart(){$par = new HTMLPurifier_Token_Start('p');$par->armor['MakeWellFormed_TagClosedError'] = true;return $par;}/*** @param HTMLPurifier_Token_Text $token*/public function handleText(&$token){$text = $token->data;// Does the current parent allow <p> tags?if ($this->allowsElement('p')) {if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {// Note that we have differing behavior when dealing with text// in the anonymous root node, or a node inside the document.// If the text as a double-newline, the treatment is the same;// if it doesn't, see the next if-block if you're in the document.$i = $nesting = null;if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {// State 1.1: ... ^ (whitespace, then document end)// ----// This is a degenerate case} else {if (!$token->is_whitespace || $this->_isInline($current)) {// State 1.2: PAR1// ----// State 1.3: PAR1\n\nPAR2// ------------// State 1.4: <div>PAR1\n\nPAR2 (see State 2)// ------------$token = array($this->_pStart());$this->_splitText($text, $token);} else {// State 1.5: \n<hr />// --}}} else {// State 2: <div>PAR1... (similar to 1.4)// ----// We're in an element that allows paragraph tags, but we're not// sure if we're going to need them.if ($this->_pLookAhead()) {// State 2.1: <div>PAR1<b>PAR1\n\nPAR2// ----// Note: This will always be the first child, since any// previous inline element would have triggered this very// same routine, and found the double newline. One possible// exception would be a comment.$token = array($this->_pStart(), $token);} else {// State 2.2.1: <div>PAR1<div>// ----// State 2.2.2: <div>PAR1<b>PAR1</b></div>// ----}}// Is the current parent a <p> tag?} elseif (!empty($this->currentNesting) &&$this->currentNesting[count($this->currentNesting) - 1]->name == 'p') {// State 3.1: ...<p>PAR1// ----// State 3.2: ...<p>PAR1\n\nPAR2// ------------$token = array();$this->_splitText($text, $token);// Abort!} else {// State 4.1: ...<b>PAR1// ----// State 4.2: ...<b>PAR1\n\nPAR2// ------------}}/*** @param HTMLPurifier_Token $token*/public function handleElement(&$token){// We don't have to check if we're already in a <p> tag for block// tokens, because the tag would have been autoclosed by MakeWellFormed.if ($this->allowsElement('p')) {if (!empty($this->currentNesting)) {if ($this->_isInline($token)) {// State 1: <div>...<b>// ---// Check if this token is adjacent to the parent token// (seek backwards until token isn't whitespace)$i = null;$this->backward($i, $prev);if (!$prev instanceof HTMLPurifier_Token_Start) {// Token wasn't adjacentif ($prev instanceof HTMLPurifier_Token_Text &&substr($prev->data, -2) === "\n\n") {// State 1.1.4: <div><p>PAR1</p>\n\n<b>// ---// Quite frankly, this should be handled by splitText$token = array($this->_pStart(), $token);} else {// State 1.1.1: <div><p>PAR1</p><b>// ---// State 1.1.2: <div><br /><b>// ---// State 1.1.3: <div>PAR<b>// ---}} else {// State 1.2.1: <div><b>// ---// Lookahead to see if <p> is needed.if ($this->_pLookAhead()) {// State 1.3.1: <div><b>PAR1\n\nPAR2// ---$token = array($this->_pStart(), $token);} else {// State 1.3.2: <div><b>PAR1</b></div>// ---// State 1.3.3: <div><b>PAR1</b><div></div>\n\n</div>// ---}}} else {// State 2.3: ...<div>// -----}} else {if ($this->_isInline($token)) {// State 3.1: <b>// ---// This is where the {p} tag is inserted, not reflected in// inputTokens yet, however.$token = array($this->_pStart(), $token);} else {// State 3.2: <div>// -----}$i = null;if ($this->backward($i, $prev)) {if (!$prev instanceof HTMLPurifier_Token_Text) {// State 3.1.1: ...</p>{p}<b>// ---// State 3.2.1: ...</p><div>// -----if (!is_array($token)) {$token = array($token);}array_unshift($token, new HTMLPurifier_Token_Text("\n\n"));} else {// State 3.1.2: ...</p>\n\n{p}<b>// ---// State 3.2.2: ...</p>\n\n<div>// -----// Note: PAR<ELEM> cannot occur because PAR would have been// wrapped in <p> tags.}}}} else {// State 2.2: <ul><li>// ----// State 2.4: <p><b>// ---}}/*** Splits up a text in paragraph tokens and appends them* to the result stream that will replace the original* @param string $data String text data that will be processed* into paragraphs* @param HTMLPurifier_Token[] $result Reference to array of tokens that the* tags will be appended onto*/private function _splitText($data, &$result){$raw_paragraphs = explode("\n\n", $data);$paragraphs = array(); // without empty paragraphs$needs_start = false;$needs_end = false;$c = count($raw_paragraphs);if ($c == 1) {// There were no double-newlines, abort quickly. In theory this// should never happen.$result[] = new HTMLPurifier_Token_Text($data);return;}for ($i = 0; $i < $c; $i++) {$par = $raw_paragraphs[$i];if (trim($par) !== '') {$paragraphs[] = $par;} else {if ($i == 0) {// Double newline at the frontif (empty($result)) {// The empty result indicates that the AutoParagraph// injector did not add any start paragraph tokens.// This means that we have been in a paragraph for// a while, and the newline means we should start a new one.$result[] = new HTMLPurifier_Token_End('p');$result[] = new HTMLPurifier_Token_Text("\n\n");// However, the start token should only be added if// there is more processing to be done (i.e. there are// real paragraphs in here). If there are none, the// next start paragraph tag will be handled by the// next call to the injector$needs_start = true;} else {// We just started a new paragraph!// Reinstate a double-newline for presentation's sake, since// it was in the source code.array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));}} elseif ($i + 1 == $c) {// Double newline at the end// There should be a trailing </p> when we're finally done.$needs_end = true;}}}// Check if this was just a giant blob of whitespace. Move this earlier,// perhaps?if (empty($paragraphs)) {return;}// Add the start tag indicated by \n\n at the beginning of $dataif ($needs_start) {$result[] = $this->_pStart();}// Append the paragraphs onto the resultforeach ($paragraphs as $par) {$result[] = new HTMLPurifier_Token_Text($par);$result[] = new HTMLPurifier_Token_End('p');$result[] = new HTMLPurifier_Token_Text("\n\n");$result[] = $this->_pStart();}// Remove trailing start token; Injector will handle this later if// it was indeed needed. This prevents from needing to do a lookahead,// at the cost of a lookbehind later.array_pop($result);// If there is no need for an end tag, remove all of it and let// MakeWellFormed close it later.if (!$needs_end) {array_pop($result); // removes \n\narray_pop($result); // removes </p>}}/*** Returns true if passed token is inline (and, ergo, allowed in* paragraph tags)* @param HTMLPurifier_Token $token* @return bool*/private function _isInline($token){return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);}/*** Looks ahead in the token list and determines whether or not we need* to insert a <p> tag.* @return bool*/private function _pLookAhead(){if ($this->currentToken instanceof HTMLPurifier_Token_Start) {$nesting = 1;} else {$nesting = 0;}$ok = false;$i = null;while ($this->forwardUntilEndToken($i, $current, $nesting)) {$result = $this->_checkNeedsP($current);if ($result !== null) {$ok = $result;break;}}return $ok;}/*** Determines if a particular token requires an earlier inline token* to get a paragraph. This should be used with _forwardUntilEndToken* @param HTMLPurifier_Token $current* @return bool*/private function _checkNeedsP($current){if ($current instanceof HTMLPurifier_Token_Start) {if (!$this->_isInline($current)) {// <div>PAR1<div>// ----// Terminate early, since we hit a block elementreturn false;}} elseif ($current instanceof HTMLPurifier_Token_Text) {if (strpos($current->data, "\n\n") !== false) {// <div>PAR1<b>PAR1\n\nPAR2// ----return true;} else {// <div>PAR1<b>PAR1...// ----}}return null;}}/*** Injector that displays the URL of an anchor instead of linking to it, in addition to showing the text of the link.*/class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector{/*** @type string*/public $name = 'DisplayLinkURI';/*** @type array*/public $needed = array('a');/*** @param $token*/public function handleElement(&$token){}/*** @param HTMLPurifier_Token $token*/public function handleEnd(&$token){if (isset($token->start->attr['href'])) {$url = $token->start->attr['href'];unset($token->start->attr['href']);$token = array($token, new HTMLPurifier_Token_Text(" ($url)"));} else {// nothing to display}}}/*** Injector that converts http, https and ftp text URLs to actual links.*/class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector{/*** @type string*/public $name = 'Linkify';/*** @type array*/public $needed = array('a' => array('href'));/*** @param HTMLPurifier_Token $token*/public function handleText(&$token){if (!$this->allowsElement('a')) {return;}if (strpos($token->data, '://') === false) {// our really quick heuristic failed, abort// this may not work so well if we want to match things like// "google.com", but then again, most people don'treturn;}// there is/are URL(s). Let's split the string.// We use this regex:// https://gist.github.com/gruber/249502// but with @cscott's backtracking fix and also// the Unicode characters un-Unicodified.$bits = preg_split('/\\b((?:[a-z][\\w\\-]+:(?:\\/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\/)(?:[^\\s()<>]|\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\))+(?:\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'".,<>?\x{00ab}\x{00bb}\x{201c}\x{201d}\x{2018}\x{2019}]))/iu',$token->data, -1, PREG_SPLIT_DELIM_CAPTURE);$token = array();// $i = index// $c = count// $l = is linkfor ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {if (!$l) {if ($bits[$i] === '') {continue;}$token[] = new HTMLPurifier_Token_Text($bits[$i]);} else {$token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));$token[] = new HTMLPurifier_Token_Text($bits[$i]);$token[] = new HTMLPurifier_Token_End('a');}}}}/*** Injector that converts configuration directive syntax %Namespace.Directive* to links*/class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector{/*** @type string*/public $name = 'PurifierLinkify';/*** @type string*/public $docURL;/*** @type array*/public $needed = array('a' => array('href'));/*** @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return string*/public function prepare($config, $context){$this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');return parent::prepare($config, $context);}/*** @param HTMLPurifier_Token $token*/public function handleText(&$token){if (!$this->allowsElement('a')) {return;}if (strpos($token->data, '%') === false) {return;}$bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);$token = array();// $i = index// $c = count// $l = is linkfor ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {if (!$l) {if ($bits[$i] === '') {continue;}$token[] = new HTMLPurifier_Token_Text($bits[$i]);} else {$token[] = new HTMLPurifier_Token_Start('a',array('href' => str_replace('%s', $bits[$i], $this->docURL)));$token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);$token[] = new HTMLPurifier_Token_End('a');}}}}class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector{/*** @type HTMLPurifier_Context*/private $context;/*** @type HTMLPurifier_Config*/private $config;/*** @type HTMLPurifier_AttrValidator*/private $attrValidator;/*** @type bool*/private $removeNbsp;/*** @type bool*/private $removeNbspExceptions;/*** Cached contents of %AutoFormat.RemoveEmpty.Predicate* @type array*/private $exclude;/*** @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return void*/public function prepare($config, $context){parent::prepare($config, $context);$this->config = $config;$this->context = $context;$this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');$this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');$this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate');foreach ($this->exclude as $key => $attrs) {if (!is_array($attrs)) {// HACK, see HTMLPurifier/Printer/ConfigForm.php$this->exclude[$key] = explode(';', $attrs);}}$this->attrValidator = new HTMLPurifier_AttrValidator();}/*** @param HTMLPurifier_Token $token*/public function handleElement(&$token){if (!$token instanceof HTMLPurifier_Token_Start) {return;}$next = false;$deleted = 1; // the current tagfor ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {$next = $this->inputZipper->back[$i];if ($next instanceof HTMLPurifier_Token_Text) {if ($next->is_whitespace) {continue;}if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {$plain = str_replace("\xC2\xA0", "", $next->data);$isWsOrNbsp = $plain === '' || ctype_space($plain);if ($isWsOrNbsp) {continue;}}}break;}if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {$this->attrValidator->validateToken($token, $this->config, $this->context);$token->armor['ValidateAttributes'] = true;if (isset($this->exclude[$token->name])) {$r = true;foreach ($this->exclude[$token->name] as $elem) {if (!isset($token->attr[$elem])) $r = false;}if ($r) return;}if (isset($token->attr['id']) || isset($token->attr['name'])) {return;}$token = $deleted + 1;for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {$prev = $this->inputZipper->front[$b];if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {continue;}break;}// This is safe because we removed the token that triggered this.$this->rewindOffset($b+$deleted);return;}}}/*** Injector that removes spans with no attributes*/class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector{/*** @type string*/public $name = 'RemoveSpansWithoutAttributes';/*** @type array*/public $needed = array('span');/*** @type HTMLPurifier_AttrValidator*/private $attrValidator;/*** Used by AttrValidator.* @type HTMLPurifier_Config*/private $config;/*** @type HTMLPurifier_Context*/private $context;public function prepare($config, $context){$this->attrValidator = new HTMLPurifier_AttrValidator();$this->config = $config;$this->context = $context;return parent::prepare($config, $context);}/*** @param HTMLPurifier_Token $token*/public function handleElement(&$token){if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) {return;}// We need to validate the attributes now since this doesn't normally// happen until after MakeWellFormed. If all the attributes are removed// the span needs to be removed too.$this->attrValidator->validateToken($token, $this->config, $this->context);$token->armor['ValidateAttributes'] = true;if (!empty($token->attr)) {return;}$nesting = 0;while ($this->forwardUntilEndToken($i, $current, $nesting)) {}if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {// Mark closing span tag for deletion$current->markForDeletion = true;// Delete open span tag$token = false;}}/*** @param HTMLPurifier_Token $token*/public function handleEnd(&$token){if ($token->markForDeletion) {$token = false;}}}/*** Adds important param elements to inside of object in order to make* things safe.*/class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector{/*** @type string*/public $name = 'SafeObject';/*** @type array*/public $needed = array('object', 'param');/*** @type array*/protected $objectStack = array();/*** @type array*/protected $paramStack = array();/*** Keep this synchronized with AttrTransform/SafeParam.php.* @type array*/protected $addParam = array('allowScriptAccess' => 'never','allowNetworking' => 'internal',);/*** These are all lower-case keys.* @type array*/protected $allowedParam = array('wmode' => true,'movie' => true,'flashvars' => true,'src' => true,'allowfullscreen' => true, // if omitted, assume to be 'false');/*** @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return void*/public function prepare($config, $context){parent::prepare($config, $context);}/*** @param HTMLPurifier_Token $token*/public function handleElement(&$token){if ($token->name == 'object') {$this->objectStack[] = $token;$this->paramStack[] = array();$new = array($token);foreach ($this->addParam as $name => $value) {$new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));}$token = $new;} elseif ($token->name == 'param') {$nest = count($this->currentNesting) - 1;if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {$i = count($this->objectStack) - 1;if (!isset($token->attr['name'])) {$token = false;return;}$n = $token->attr['name'];// We need this fix because YouTube doesn't supply a data// attribute, which we need if a type is specified. This is// *very* Flash specific.if (!isset($this->objectStack[$i]->attr['data']) &&($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')) {$this->objectStack[$i]->attr['data'] = $token->attr['value'];}// Check if the parameter is the correct value but has not// already been addedif (!isset($this->paramStack[$i][$n]) &&isset($this->addParam[$n]) &&$token->attr['name'] === $this->addParam[$n]) {// keep token, and add to param stack$this->paramStack[$i][$n] = true;} elseif (isset($this->allowedParam[strtolower($n)])) {// keep token, don't do anything to it// (could possibly check for duplicates here)// Note: In principle, parameters should be case sensitive.// But it seems they are not really; so accept any case.} else {$token = false;}} else {// not directly inside an object, DENY!$token = false;}}}public function handleEnd(&$token){// This is the WRONG way of handling the object and param stacks;// we should be inserting them directly on the relevant object tokens// so that the global stack handling handles it.if ($token->name == 'object') {array_pop($this->objectStack);array_pop($this->paramStack);}}}/*** Parser that uses PHP 5's DOM extension (part of the core).** In PHP 5, the DOM XML extension was revamped into DOM and added to the core.* It gives us a forgiving HTML parser, which we use to transform the HTML* into a DOM, and then into the tokens. It is blazingly fast (for large* documents, it performs twenty times faster than* HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.** @note Any empty elements will have empty tokens associated with them, even if* this is prohibited by the spec. This is cannot be fixed until the spec* comes into play.** @note PHP's DOM extension does not actually parse any entities, we use* our own function to do that.** @warning DOM tends to drop whitespace, which may wreak havoc on indenting.* If this is a huge problem, due to the fact that HTML is hand* edited and you are unable to get a parser cache that caches the* the output of HTML Purifier while keeping the original HTML lying* around, you may want to run Tidy on the resulting output or use* HTMLPurifier_DirectLex*/class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer{/*** @type HTMLPurifier_TokenFactory*/private $factory;public function __construct(){// setup the factoryparent::__construct();$this->factory = new HTMLPurifier_TokenFactory();}/*** @param string $html* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_Token[]*/public function tokenizeHTML($html, $config, $context){$html = $this->normalize($html, $config, $context);// attempt to armor stray angled brackets that cannot possibly// form tags and thus are probably being used as emoticonsif ($config->get('Core.AggressivelyFixLt')) {$char = '[^a-z!\/]';$comment = "/<!--(.*?)(-->|\z)/is";$html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);do {$old = $html;$html = preg_replace("/<($char)/i", '<\\1', $html);} while ($html !== $old);$html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments}// preprocess html, essential for UTF-8$html = $this->wrapHTML($html, $config, $context);$doc = new DOMDocument();$doc->encoding = 'UTF-8'; // theoretically, the above has this covered$options = 0;if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) {$options |= LIBXML_PARSEHUGE;}set_error_handler(array($this, 'muteErrorHandler'));// loadHTML() fails on PHP 5.3 when second parameter is givenif ($options) {$doc->loadHTML($html, $options);} else {$doc->loadHTML($html);}restore_error_handler();$body = $doc->getElementsByTagName('html')->item(0)-> // <html>getElementsByTagName('body')->item(0); // <body>$div = $body->getElementsByTagName('div')->item(0); // <div>$tokens = array();$this->tokenizeDOM($div, $tokens, $config);// If the div has a sibling, that means we tripped across// a premature </div> tag. So remove the div we parsed,// and then tokenize the rest of body. We can't tokenize// the sibling directly as we'll lose the tags in that case.if ($div->nextSibling) {$body->removeChild($div);$this->tokenizeDOM($body, $tokens, $config);}return $tokens;}/*** Iterative function that tokenizes a node, putting it into an accumulator.* To iterate is human, to recurse divine - L. Peter Deutsch* @param DOMNode $node DOMNode to be tokenized.* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.* @return HTMLPurifier_Token of node appended to previously passed tokens.*/protected function tokenizeDOM($node, &$tokens, $config){$level = 0;$nodes = array($level => new HTMLPurifier_Queue(array($node)));$closingNodes = array();do {while (!$nodes[$level]->isEmpty()) {$node = $nodes[$level]->shift(); // FIFO$collect = $level > 0 ? true : false;$needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);if ($needEndingTag) {$closingNodes[$level][] = $node;}if ($node->childNodes && $node->childNodes->length) {$level++;$nodes[$level] = new HTMLPurifier_Queue();foreach ($node->childNodes as $childNode) {$nodes[$level]->push($childNode);}}}$level--;if ($level && isset($closingNodes[$level])) {while ($node = array_pop($closingNodes[$level])) {$this->createEndNode($node, $tokens);}}} while ($level > 0);}/*** Portably retrieve the tag name of a node; deals with older versions* of libxml like 2.7.6* @param DOMNode $node*/protected function getTagName($node){if (isset($node->tagName)) {return $node->tagName;} else if (isset($node->nodeName)) {return $node->nodeName;} else if (isset($node->localName)) {return $node->localName;}return null;}/*** Portably retrieve the data of a node; deals with older versions* of libxml like 2.7.6* @param DOMNode $node*/protected function getData($node){if (isset($node->data)) {return $node->data;} else if (isset($node->nodeValue)) {return $node->nodeValue;} else if (isset($node->textContent)) {return $node->textContent;}return null;}/*** @param DOMNode $node DOMNode to be tokenized.* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.* @param bool $collect Says whether or start and close are collected, set to* false at first recursion because it's the implicit DIV* tag you're dealing with.* @return bool if the token needs an endtoken* @todo data and tagName properties don't seem to exist in DOMNode?*/protected function createStartNode($node, &$tokens, $collect, $config){// intercept non element nodes. WE MUST catch all of them,// but we're not getting the character reference nodes because// those should have been preprocessedif ($node->nodeType === XML_TEXT_NODE) {$data = $this->getData($node); // Handle variable data propertyif ($data !== null) {$tokens[] = $this->factory->createText($data);}return false;} elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {// undo libxml's special treatment of <script> and <style> tags$last = end($tokens);$data = $node->data;// (note $node->tagname is already normalized)if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {$new_data = trim($data);if (substr($new_data, 0, 4) === '<!--') {$data = substr($new_data, 4);if (substr($data, -3) === '-->') {$data = substr($data, 0, -3);} else {// Highly suspicious! Not sure what to do...}}}$tokens[] = $this->factory->createText($this->parseText($data, $config));return false;} elseif ($node->nodeType === XML_COMMENT_NODE) {// this is code is only invoked for comments in script/style in versions// of libxml pre-2.6.28 (regular comments, of course, are still// handled regularly)$tokens[] = $this->factory->createComment($node->data);return false;} elseif ($node->nodeType !== XML_ELEMENT_NODE) {// not-well tested: there may be other nodes we have to grabreturn false;}$attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();$tag_name = $this->getTagName($node); // Handle variable tagName propertyif (empty($tag_name)) {return (bool) $node->childNodes->length;}// We still have to make sure that the element actually IS emptyif (!$node->childNodes->length) {if ($collect) {$tokens[] = $this->factory->createEmpty($tag_name, $attr);}return false;} else {if ($collect) {$tokens[] = $this->factory->createStart($tag_name, $attr);}return true;}}/*** @param DOMNode $node* @param HTMLPurifier_Token[] $tokens*/protected function createEndNode($node, &$tokens){$tag_name = $this->getTagName($node); // Handle variable tagName property$tokens[] = $this->factory->createEnd($tag_name);}/*** Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.** @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.* @return array Associative array of attributes.*/protected function transformAttrToAssoc($node_map){// NamedNodeMap is documented very well, so we're using undocumented// features, namely, the fact that it implements Iterator and// has a ->length attributeif ($node_map->length === 0) {return array();}$array = array();foreach ($node_map as $attr) {$array[$attr->name] = $attr->value;}return $array;}/*** An error handler that mutes all errors* @param int $errno* @param string $errstr*/public function muteErrorHandler($errno, $errstr){}/*** Callback function for undoing escaping of stray angled brackets* in comments* @param array $matches* @return string*/public function callbackUndoCommentSubst($matches){return '<!--' . strtr($matches[1], array('&' => '&', '<' => '<')) . $matches[2];}/*** Callback function that entity-izes ampersands in comments so that* callbackUndoCommentSubst doesn't clobber them* @param array $matches* @return string*/public function callbackArmorCommentEntities($matches){return '<!--' . str_replace('&', '&', $matches[1]) . $matches[2];}/*** Wraps an HTML fragment in the necessary HTML* @param string $html* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return string*/protected function wrapHTML($html, $config, $context, $use_div = true){$def = $config->getDefinition('HTML');$ret = '';if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {$ret .= '<!DOCTYPE html ';if (!empty($def->doctype->dtdPublic)) {$ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';}if (!empty($def->doctype->dtdSystem)) {$ret .= '"' . $def->doctype->dtdSystem . '" ';}$ret .= '>';}$ret .= '<html><head>';$ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';// No protection if $html contains a stray </div>!$ret .= '</head><body>';if ($use_div) $ret .= '<div>';$ret .= $html;if ($use_div) $ret .= '</div>';$ret .= '</body></html>';return $ret;}}/*** Our in-house implementation of a parser.** A pure PHP parser, DirectLex has absolutely no dependencies, making* it a reasonably good default for PHP4. Written with efficiency in mind,* it can be four times faster than HTMLPurifier_Lexer_PEARSax3, although it* pales in comparison to HTMLPurifier_Lexer_DOMLex.** @todo Reread XML spec and document differences.*/class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer{/*** @type bool*/public $tracksLineNumbers = true;/*** Whitespace characters for str(c)spn.* @type string*/protected $_whitespace = "\x20\x09\x0D\x0A";/*** Callback function for script CDATA fudge* @param array $matches, in form of array(opening tag, contents, closing tag)* @return string*/protected function scriptCallback($matches){return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];}/*** @param String $html* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return array|HTMLPurifier_Token[]*/public function tokenizeHTML($html, $config, $context){// special normalization for script tags without any armor// our "armor" heurstic is a < sign any number of whitespaces after// the first script tagif ($config->get('HTML.Trusted')) {$html = preg_replace_callback('#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',array($this, 'scriptCallback'),$html);}$html = $this->normalize($html, $config, $context);$cursor = 0; // our location in the text$inside_tag = false; // whether or not we're parsing the inside of a tag$array = array(); // result array// This is also treated to mean maintain *column* numbers too$maintain_line_numbers = $config->get('Core.MaintainLineNumbers');if ($maintain_line_numbers === null) {// automatically determine line numbering by checking// if error collection is on$maintain_line_numbers = $config->get('Core.CollectErrors');}if ($maintain_line_numbers) {$current_line = 1;$current_col = 0;$length = strlen($html);} else {$current_line = false;$current_col = false;$length = false;}$context->register('CurrentLine', $current_line);$context->register('CurrentCol', $current_col);$nl = "\n";// how often to manually recalculate. This will ALWAYS be right,// but it's pretty wasteful. Set to 0 to turn off$synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');$e = false;if ($config->get('Core.CollectErrors')) {$e =& $context->get('ErrorCollector');}// for testing synchronization$loops = 0;while (++$loops) {// $cursor is either at the start of a token, or inside of// a tag (i.e. there was a < immediately before it), as indicated// by $inside_tagif ($maintain_line_numbers) {// $rcursor, however, is always at the start of a token.$rcursor = $cursor - (int)$inside_tag;// Column number is cheap, so we calculate it every round.// We're interested at the *end* of the newline string, so// we need to add strlen($nl) == 1 to $nl_pos before subtracting it// from our "rcursor" position.$nl_pos = strrpos($html, $nl, $rcursor - $length);$current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);// recalculate linesif ($synchronize_interval && // synchronization is on$cursor > 0 && // cursor is further than zero$loops % $synchronize_interval === 0) { // time to synchronize!$current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);}}$position_next_lt = strpos($html, '<', $cursor);$position_next_gt = strpos($html, '>', $cursor);// triggers on "<b>asdf</b>" but not "asdf <b></b>"// special case to set up contextif ($position_next_lt === $cursor) {$inside_tag = true;$cursor++;}if (!$inside_tag && $position_next_lt !== false) {// We are not inside tag and there still is another tag to parse$token = newHTMLPurifier_Token_Text($this->parseText(substr($html,$cursor,$position_next_lt - $cursor), $config));if ($maintain_line_numbers) {$token->rawPosition($current_line, $current_col);$current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);}$array[] = $token;$cursor = $position_next_lt + 1;$inside_tag = true;continue;} elseif (!$inside_tag) {// We are not inside tag but there are no more tags// If we're already at the end, breakif ($cursor === strlen($html)) {break;}// Create Text of rest of string$token = newHTMLPurifier_Token_Text($this->parseText(substr($html,$cursor), $config));if ($maintain_line_numbers) {$token->rawPosition($current_line, $current_col);}$array[] = $token;break;} elseif ($inside_tag && $position_next_gt !== false) {// We are in tag and it is well formed// Grab the internals of the tag$strlen_segment = $position_next_gt - $cursor;if ($strlen_segment < 1) {// there's nothing to process!$token = new HTMLPurifier_Token_Text('<');$cursor++;continue;}$segment = substr($html, $cursor, $strlen_segment);if ($segment === false) {// somehow, we attempted to access beyond the end of// the string, defense-in-depth, reported by Nate Abelebreak;}// Check if it's a commentif (substr($segment, 0, 3) === '!--') {// re-determine segment length, looking for -->$position_comment_end = strpos($html, '-->', $cursor);if ($position_comment_end === false) {// uh oh, we have a comment that extends to// infinity. Can't be helped: set comment// end position to end of stringif ($e) {$e->send(E_WARNING, 'Lexer: Unclosed comment');}$position_comment_end = strlen($html);$end = true;} else {$end = false;}$strlen_segment = $position_comment_end - $cursor;$segment = substr($html, $cursor, $strlen_segment);$token = newHTMLPurifier_Token_Comment(substr($segment,3,$strlen_segment - 3));if ($maintain_line_numbers) {$token->rawPosition($current_line, $current_col);$current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);}$array[] = $token;$cursor = $end ? $position_comment_end : $position_comment_end + 3;$inside_tag = false;continue;}// Check if it's an end tag$is_end_tag = (strpos($segment, '/') === 0);if ($is_end_tag) {$type = substr($segment, 1);$token = new HTMLPurifier_Token_End($type);if ($maintain_line_numbers) {$token->rawPosition($current_line, $current_col);$current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);}$array[] = $token;$inside_tag = false;$cursor = $position_next_gt + 1;continue;}// Check leading character is alnum, if not, we may// have accidently grabbed an emoticon. Translate into// text and go our merry wayif (!ctype_alpha($segment[0])) {// XML: $segment[0] !== '_' && $segment[0] !== ':'if ($e) {$e->send(E_NOTICE, 'Lexer: Unescaped lt');}$token = new HTMLPurifier_Token_Text('<');if ($maintain_line_numbers) {$token->rawPosition($current_line, $current_col);$current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);}$array[] = $token;$inside_tag = false;continue;}// Check if it is explicitly self closing, if so, remove// trailing slash. Remember, we could have a tag like <br>, so// any later token processing scripts must convert improperly// classified EmptyTags from StartTags.$is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);if ($is_self_closing) {$strlen_segment--;$segment = substr($segment, 0, $strlen_segment);}// Check if there are any attributes$position_first_space = strcspn($segment, $this->_whitespace);if ($position_first_space >= $strlen_segment) {if ($is_self_closing) {$token = new HTMLPurifier_Token_Empty($segment);} else {$token = new HTMLPurifier_Token_Start($segment);}if ($maintain_line_numbers) {$token->rawPosition($current_line, $current_col);$current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);}$array[] = $token;$inside_tag = false;$cursor = $position_next_gt + 1;continue;}// Grab out all the data$type = substr($segment, 0, $position_first_space);$attribute_string =trim(substr($segment,$position_first_space));if ($attribute_string) {$attr = $this->parseAttributeString($attribute_string,$config,$context);} else {$attr = array();}if ($is_self_closing) {$token = new HTMLPurifier_Token_Empty($type, $attr);} else {$token = new HTMLPurifier_Token_Start($type, $attr);}if ($maintain_line_numbers) {$token->rawPosition($current_line, $current_col);$current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);}$array[] = $token;$cursor = $position_next_gt + 1;$inside_tag = false;continue;} else {// inside tag, but there's no ending > signif ($e) {$e->send(E_WARNING, 'Lexer: Missing gt');}$token = newHTMLPurifier_Token_Text('<' .$this->parseText(substr($html, $cursor), $config));if ($maintain_line_numbers) {$token->rawPosition($current_line, $current_col);}// no cursor scroll? Hmm...$array[] = $token;break;}break;}$context->destroy('CurrentLine');$context->destroy('CurrentCol');return $array;}/*** PHP 5.0.x compatible substr_count that implements offset and length* @param string $haystack* @param string $needle* @param int $offset* @param int $length* @return int*/protected function substrCount($haystack, $needle, $offset, $length){static $oldVersion;if ($oldVersion === null) {$oldVersion = version_compare(PHP_VERSION, '5.1', '<');}if ($oldVersion) {$haystack = substr($haystack, $offset, $length);return substr_count($haystack, $needle);} else {return substr_count($haystack, $needle, $offset, $length);}}/*** Takes the inside of an HTML tag and makes an assoc array of attributes.** @param string $string Inside of tag excluding name.* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return array Assoc array of attributes.*/public function parseAttributeString($string, $config, $context){$string = (string)$string; // quick typecastif ($string == '') {return array();} // no attributes$e = false;if ($config->get('Core.CollectErrors')) {$e =& $context->get('ErrorCollector');}// let's see if we can abort as quickly as possible// one equal sign, no spaces => one attribute$num_equal = substr_count($string, '=');$has_space = strpos($string, ' ');if ($num_equal === 0 && !$has_space) {// bool attributereturn array($string => $string);} elseif ($num_equal === 1 && !$has_space) {// only one attributelist($key, $quoted_value) = explode('=', $string);$quoted_value = trim($quoted_value);if (!$key) {if ($e) {$e->send(E_ERROR, 'Lexer: Missing attribute key');}return array();}if (!$quoted_value) {return array($key => '');}$first_char = @$quoted_value[0];$last_char = @$quoted_value[strlen($quoted_value) - 1];$same_quote = ($first_char == $last_char);$open_quote = ($first_char == '"' || $first_char == "'");if ($same_quote && $open_quote) {// well behaved$value = substr($quoted_value, 1, strlen($quoted_value) - 2);} else {// not well behavedif ($open_quote) {if ($e) {$e->send(E_ERROR, 'Lexer: Missing end quote');}$value = substr($quoted_value, 1);} else {$value = $quoted_value;}}if ($value === false) {$value = '';}return array($key => $this->parseAttr($value, $config));}// setup loop environment$array = array(); // return assoc array of attributes$cursor = 0; // current position in string (moves forward)$size = strlen($string); // size of the string (stays the same)// if we have unquoted attributes, the parser expects a terminating// space, so let's guarantee that there's always a terminating space.$string .= ' ';$old_cursor = -1;while ($cursor < $size) {if ($old_cursor >= $cursor) {throw new Exception("Infinite loop detected");}$old_cursor = $cursor;$cursor += ($value = strspn($string, $this->_whitespace, $cursor));// grab the key$key_begin = $cursor; //we're currently at the start of the key// scroll past all characters that are the key (not whitespace or =)$cursor += strcspn($string, $this->_whitespace . '=', $cursor);$key_end = $cursor; // now at the end of the key$key = substr($string, $key_begin, $key_end - $key_begin);if (!$key) {if ($e) {$e->send(E_ERROR, 'Lexer: Missing attribute key');}$cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loopcontinue; // empty key}// scroll past all whitespace$cursor += strspn($string, $this->_whitespace, $cursor);if ($cursor >= $size) {$array[$key] = $key;break;}// if the next character is an equal sign, we've got a regular// pair, otherwise, it's a bool attribute$first_char = @$string[$cursor];if ($first_char == '=') {// key="value"$cursor++;$cursor += strspn($string, $this->_whitespace, $cursor);if ($cursor === false) {$array[$key] = '';break;}// we might be in front of a quote right now$char = @$string[$cursor];if ($char == '"' || $char == "'") {// it's quoted, end bound is $char$cursor++;$value_begin = $cursor;$cursor = strpos($string, $char, $cursor);$value_end = $cursor;} else {// it's not quoted, end bound is whitespace$value_begin = $cursor;$cursor += strcspn($string, $this->_whitespace, $cursor);$value_end = $cursor;}// we reached a premature endif ($cursor === false) {$cursor = $size;$value_end = $cursor;}$value = substr($string, $value_begin, $value_end - $value_begin);if ($value === false) {$value = '';}$array[$key] = $this->parseAttr($value, $config);$cursor++;} else {// boolattrif ($key !== '') {$array[$key] = $key;} else {// purely theoreticalif ($e) {$e->send(E_ERROR, 'Lexer: Missing attribute key');}}}}return $array;}}/*** Concrete comment node class.*/class HTMLPurifier_Node_Comment extends HTMLPurifier_Node{/*** Character data within comment.* @type string*/public $data;/*** @type bool*/public $is_whitespace = true;/*** Transparent constructor.** @param string $data String comment data.* @param int $line* @param int $col*/public function __construct($data, $line = null, $col = null){$this->data = $data;$this->line = $line;$this->col = $col;}public function toTokenPair() {return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);}}/*** Concrete element node class.*/class HTMLPurifier_Node_Element extends HTMLPurifier_Node{/*** The lower-case name of the tag, like 'a', 'b' or 'blockquote'.** @note Strictly speaking, XML tags are case sensitive, so we shouldn't* be lower-casing them, but these tokens cater to HTML tags, which are* insensitive.* @type string*/public $name;/*** Associative array of the node's attributes.* @type array*/public $attr = array();/*** List of child elements.* @type array*/public $children = array();/*** Does this use the <a></a> form or the </a> form, i.e.* is it a pair of start/end tokens or an empty token.* @bool*/public $empty = false;public $endCol = null, $endLine = null, $endArmor = array();public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {$this->name = $name;$this->attr = $attr;$this->line = $line;$this->col = $col;$this->armor = $armor;}public function toTokenPair() {// XXX inefficiency here, normalization is not necessaryif ($this->empty) {return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);} else {$start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);$end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);//$end->start = $start;return array($start, $end);}}}/*** Concrete text token class.** Text tokens comprise of regular parsed character data (PCDATA) and raw* character data (from the CDATA sections). Internally, their* data is parsed with all entities expanded. Surprisingly, the text token* does have a "tag name" called #PCDATA, which is how the DTD represents it* in permissible child nodes.*/class HTMLPurifier_Node_Text extends HTMLPurifier_Node{/*** PCDATA tag name compatible with DTD, see* HTMLPurifier_ChildDef_Custom for details.* @type string*/public $name = '#PCDATA';/*** @type string*/public $data;/**< Parsed character data of text. *//*** @type bool*/public $is_whitespace;/**< Bool indicating if node is whitespace. *//*** Constructor, accepts data and determines if it is whitespace.* @param string $data String parsed character data.* @param int $line* @param int $col*/public function __construct($data, $is_whitespace, $line = null, $col = null){$this->data = $data;$this->is_whitespace = $is_whitespace;$this->line = $line;$this->col = $col;}public function toTokenPair() {return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);}}/*** Composite strategy that runs multiple strategies on tokens.*/abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy{/*** List of strategies to run tokens through.* @type HTMLPurifier_Strategy[]*/protected $strategies = array();/*** @param HTMLPurifier_Token[] $tokens* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_Token[]*/public function execute($tokens, $config, $context){foreach ($this->strategies as $strategy) {$tokens = $strategy->execute($tokens, $config, $context);}return $tokens;}}/*** Core strategy composed of the big four strategies.*/class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite{public function __construct(){$this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();$this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();$this->strategies[] = new HTMLPurifier_Strategy_FixNesting();$this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();}}/*** Takes a well formed list of tokens and fixes their nesting.** HTML elements dictate which elements are allowed to be their children,* for example, you can't have a p tag in a span tag. Other elements have* much more rigorous definitions: tables, for instance, require a specific* order for their elements. There are also constraints not expressible by* document type definitions, such as the chameleon nature of ins/del* tags and global child exclusions.** The first major objective of this strategy is to iterate through all* the nodes and determine whether or not their children conform to the* element's definition. If they do not, the child definition may* optionally supply an amended list of elements that is valid or* require that the entire node be deleted (and the previous node* rescanned).** The second objective is to ensure that explicitly excluded elements of* an element do not appear in its children. Code that accomplishes this* task is pervasive through the strategy, though the two are distinct tasks* and could, theoretically, be seperated (although it's not recommended).** @note Whether or not unrecognized children are silently dropped or* translated into text depends on the child definitions.** @todo Enable nodes to be bubbled out of the structure. This is* easier with our new algorithm.*/class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy{/*** @param HTMLPurifier_Token[] $tokens* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return array|HTMLPurifier_Token[]*/public function execute($tokens, $config, $context){//####################################################################//// Pre-processing// O(n) pass to convert to a tree, so that we can efficiently// refer to substrings$top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);// get a copy of the HTML definition$definition = $config->getHTMLDefinition();$excludes_enabled = !$config->get('Core.DisableExcludes');// setup the context variable 'IsInline', for chameleon processing// is 'false' when we are not inline, 'true' when it must always// be inline, and an integer when it is inline for a certain// branch of the document tree$is_inline = $definition->info_parent_def->descendants_are_inline;$context->register('IsInline', $is_inline);// setup error collector$e =& $context->get('ErrorCollector', true);//####################################################################//// Loop initialization// stack that contains all elements that are excluded// it is organized by parent elements, similar to $stack,// but it is only populated when an element with exclusions is// processed, i.e. there won't be empty exclusions.$exclude_stack = array($definition->info_parent_def->excludes);// variable that contains the start token while we are processing// nodes. This enables error reporting to do its job$node = $top_node;// dummy tokenlist($token, $d) = $node->toTokenPair();$context->register('CurrentNode', $node);$context->register('CurrentToken', $token);//####################################################################//// Loop// We need to implement a post-order traversal iteratively, to// avoid running into stack space limits. This is pretty tricky// to reason about, so we just manually stack-ify the recursive// variant://// function f($node) {// foreach ($node->children as $child) {// f($child);// }// validate($node);// }//// Thus, we will represent a stack frame as array($node,// $is_inline, stack of children)// e.g. array_reverse($node->children) - already processed// children.$parent_def = $definition->info_parent_def;$stack = array(array($top_node,$parent_def->descendants_are_inline,$parent_def->excludes, // exclusions0));while (!empty($stack)) {list($node, $is_inline, $excludes, $ix) = array_pop($stack);// recursive call$go = false;$def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];while (isset($node->children[$ix])) {$child = $node->children[$ix++];if ($child instanceof HTMLPurifier_Node_Element) {$go = true;$stack[] = array($node, $is_inline, $excludes, $ix);$stack[] = array($child,// ToDo: I don't think it matters if it's def or// child_def, but double check this...$is_inline || $def->descendants_are_inline,empty($def->excludes) ? $excludes: array_merge($excludes, $def->excludes),0);break;}};if ($go) continue;list($token, $d) = $node->toTokenPair();// base caseif ($excludes_enabled && isset($excludes[$node->name])) {$node->dead = true;if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');} else {// XXX I suppose it would be slightly more efficient to// avoid the allocation here and have children// strategies handle it$children = array();foreach ($node->children as $child) {if (!$child->dead) $children[] = $child;}$result = $def->child->validateChildren($children, $config, $context);if ($result === true) {// nop$node->children = $children;} elseif ($result === false) {$node->dead = true;if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');} else {$node->children = $result;if ($e) {// XXX This will miss mutations of internal nodes. Perhaps defer to the child validatorsif (empty($result) && !empty($children)) {$e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');} else if ($result != $children) {$e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');}}}}}//####################################################################//// Post-processing// remove context variables$context->destroy('IsInline');$context->destroy('CurrentNode');$context->destroy('CurrentToken');//####################################################################//// Returnreturn HTMLPurifier_Arborize::flatten($node, $config, $context);}}/*** Takes tokens makes them well-formed (balance end tags, etc.)** Specification of the armor attributes this strategy uses:** - MakeWellFormed_TagClosedError: This armor field is used to* suppress tag closed errors for certain tokens [TagClosedSuppress],* in particular, if a tag was generated automatically by HTML* Purifier, we may rely on our infrastructure to close it for us* and shouldn't report an error to the user [TagClosedAuto].*/class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy{/*** Array stream of tokens being processed.* @type HTMLPurifier_Token[]*/protected $tokens;/*** Current token.* @type HTMLPurifier_Token*/protected $token;/*** Zipper managing the true state.* @type HTMLPurifier_Zipper*/protected $zipper;/*** Current nesting of elements.* @type array*/protected $stack;/*** Injectors active in this stream processing.* @type HTMLPurifier_Injector[]*/protected $injectors;/*** Current instance of HTMLPurifier_Config.* @type HTMLPurifier_Config*/protected $config;/*** Current instance of HTMLPurifier_Context.* @type HTMLPurifier_Context*/protected $context;/*** @param HTMLPurifier_Token[] $tokens* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_Token[]* @throws HTMLPurifier_Exception*/public function execute($tokens, $config, $context){$definition = $config->getHTMLDefinition();// local variables$generator = new HTMLPurifier_Generator($config, $context);$escape_invalid_tags = $config->get('Core.EscapeInvalidTags');// used for autoclose early abortion$global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);$e = $context->get('ErrorCollector', true);$i = false; // injector indexlist($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);if ($token === NULL) {return array();}$reprocess = false; // whether or not to reprocess the same token$stack = array();// member variables$this->stack =& $stack;$this->tokens =& $tokens;$this->token =& $token;$this->zipper =& $zipper;$this->config = $config;$this->context = $context;// context variables$context->register('CurrentNesting', $stack);$context->register('InputZipper', $zipper);$context->register('CurrentToken', $token);// -- begin INJECTOR --$this->injectors = array();$injectors = $config->getBatch('AutoFormat');$def_injectors = $definition->info_injector;$custom_injectors = $injectors['Custom'];unset($injectors['Custom']); // special caseforeach ($injectors as $injector => $b) {// XXX: Fix with a legitimate lookup table of enabled filtersif (strpos($injector, '.') !== false) {continue;}$injector = "HTMLPurifier_Injector_$injector";if (!$b) {continue;}$this->injectors[] = new $injector;}foreach ($def_injectors as $injector) {// assumed to be objects$this->injectors[] = $injector;}foreach ($custom_injectors as $injector) {if (!$injector) {continue;}if (is_string($injector)) {$injector = "HTMLPurifier_Injector_$injector";$injector = new $injector;}$this->injectors[] = $injector;}// give the injectors references to the definition and context// variables for performance reasonsforeach ($this->injectors as $ix => $injector) {$error = $injector->prepare($config, $context);if (!$error) {continue;}array_splice($this->injectors, $ix, 1); // rm the injectortrigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);}// -- end INJECTOR --// a note on reprocessing:// In order to reduce code duplication, whenever some code needs// to make HTML changes in order to make things "correct", the// new HTML gets sent through the purifier, regardless of its// status. This means that if we add a start token, because it// was totally necessary, we don't have to update nesting; we just// punt ($reprocess = true; continue;) and it does that for us.// isset is in loop because $tokens size changes during loop execfor (;;// only increment if we don't need to reprocess$reprocess ? $reprocess = false : $token = $zipper->next($token)) {// check for a rewindif (is_int($i)) {// possibility: disable rewinding if the current token has a// rewind set on it already. This would offer protection from// infinite loop, but might hinder some advanced rewinding.$rewind_offset = $this->injectors[$i]->getRewindOffset();if (is_int($rewind_offset)) {for ($j = 0; $j < $rewind_offset; $j++) {if (empty($zipper->front)) break;$token = $zipper->prev($token);// indicate that other injectors should not process this token,// but we need to reprocess it. See Note [Injector skips]unset($token->skip[$i]);$token->rewind = $i;if ($token instanceof HTMLPurifier_Token_Start) {array_pop($this->stack);} elseif ($token instanceof HTMLPurifier_Token_End) {$this->stack[] = $token->start;}}}$i = false;}// handle case of document endif ($token === NULL) {// kill processing if stack is emptyif (empty($this->stack)) {break;}// peek$top_nesting = array_pop($this->stack);$this->stack[] = $top_nesting;// send error [TagClosedSuppress]if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {$e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);}// append, don't splice, since this is the end$token = new HTMLPurifier_Token_End($top_nesting->name);// punt!$reprocess = true;continue;}//echo '<br>'; printZipper($zipper, $token);//printTokens($this->stack);//flush();// quick-check: if it's not a tag, no need to processif (empty($token->is_tag)) {if ($token instanceof HTMLPurifier_Token_Text) {foreach ($this->injectors as $i => $injector) {if (isset($token->skip[$i])) {// See Note [Injector skips]continue;}if ($token->rewind !== null && $token->rewind !== $i) {continue;}// XXX fuckup$r = $token;$injector->handleText($r);$token = $this->processToken($r, $i);$reprocess = true;break;}}// another possibility is a commentcontinue;}if (isset($definition->info[$token->name])) {$type = $definition->info[$token->name]->child->type;} else {$type = false; // Type is unknown, treat accordingly}// quick tag checks: anything that's *not* an end tag$ok = false;if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {// claims to be a start tag but is empty$token = new HTMLPurifier_Token_Empty($token->name,$token->attr,$token->line,$token->col,$token->armor);$ok = true;} elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {// claims to be empty but really is a start tag// NB: this assignment is required$old_token = $token;$token = new HTMLPurifier_Token_End($token->name);$token = $this->insertBefore(new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor));// punt (since we had to modify the input stream in a non-trivial way)$reprocess = true;continue;} elseif ($token instanceof HTMLPurifier_Token_Empty) {// real empty token$ok = true;} elseif ($token instanceof HTMLPurifier_Token_Start) {// start tag// ...unless they also have to close their parentif (!empty($this->stack)) {// Performance note: you might think that it's rather// inefficient, recalculating the autoclose information// for every tag that a token closes (since when we// do an autoclose, we push a new token into the// stream and then /process/ that, before// re-processing this token.) But this is// necessary, because an injector can make an// arbitrary transformations to the autoclosing// tokens we introduce, so things may have changed// in the meantime. Also, doing the inefficient thing is// "easy" to reason about (for certain perverse definitions// of "easy")$parent = array_pop($this->stack);$this->stack[] = $parent;$parent_def = null;$parent_elements = null;$autoclose = false;if (isset($definition->info[$parent->name])) {$parent_def = $definition->info[$parent->name];$parent_elements = $parent_def->child->getAllowedElements($config);$autoclose = !isset($parent_elements[$token->name]);}if ($autoclose && $definition->info[$token->name]->wrap) {// Check if an element can be wrapped by another// element to make it valid in a context (for// example, <ul><ul> needs a <li> in between)$wrapname = $definition->info[$token->name]->wrap;$wrapdef = $definition->info[$wrapname];$elements = $wrapdef->child->getAllowedElements($config);if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {$newtoken = new HTMLPurifier_Token_Start($wrapname);$token = $this->insertBefore($newtoken);$reprocess = true;continue;}}$carryover = false;if ($autoclose && $parent_def->formatting) {$carryover = true;}if ($autoclose) {// check if this autoclose is doomed to fail// (this rechecks $parent, which his harmless)$autoclose_ok = isset($global_parent_allowed_elements[$token->name]);if (!$autoclose_ok) {foreach ($this->stack as $ancestor) {$elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);if (isset($elements[$token->name])) {$autoclose_ok = true;break;}if ($definition->info[$token->name]->wrap) {$wrapname = $definition->info[$token->name]->wrap;$wrapdef = $definition->info[$wrapname];$wrap_elements = $wrapdef->child->getAllowedElements($config);if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {$autoclose_ok = true;break;}}}}if ($autoclose_ok) {// errors need to be updated$new_token = new HTMLPurifier_Token_End($parent->name);$new_token->start = $parent;// [TagClosedSuppress]if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {if (!$carryover) {$e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);} else {$e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);}}if ($carryover) {$element = clone $parent;// [TagClosedAuto]$element->armor['MakeWellFormed_TagClosedError'] = true;$element->carryover = true;$token = $this->processToken(array($new_token, $token, $element));} else {$token = $this->insertBefore($new_token);}} else {$token = $this->remove();}$reprocess = true;continue;}}$ok = true;}if ($ok) {foreach ($this->injectors as $i => $injector) {if (isset($token->skip[$i])) {// See Note [Injector skips]continue;}if ($token->rewind !== null && $token->rewind !== $i) {continue;}$r = $token;$injector->handleElement($r);$token = $this->processToken($r, $i);$reprocess = true;break;}if (!$reprocess) {// ah, nothing interesting happened; do normal processingif ($token instanceof HTMLPurifier_Token_Start) {$this->stack[] = $token;} elseif ($token instanceof HTMLPurifier_Token_End) {throw new HTMLPurifier_Exception('Improper handling of end tag in start code; possible error in MakeWellFormed');}}continue;}// sanity check: we should be dealing with a closing tagif (!$token instanceof HTMLPurifier_Token_End) {throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');}// make sure that we have something openif (empty($this->stack)) {if ($escape_invalid_tags) {if ($e) {$e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');}$token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));} else {if ($e) {$e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');}$token = $this->remove();}$reprocess = true;continue;}// first, check for the simplest case: everything closes neatly.// Eventually, everything passes through here; if there are problems// we modify the input stream accordingly and then punt, so that// the tokens get processed again.$current_parent = array_pop($this->stack);if ($current_parent->name == $token->name) {$token->start = $current_parent;foreach ($this->injectors as $i => $injector) {if (isset($token->skip[$i])) {// See Note [Injector skips]continue;}if ($token->rewind !== null && $token->rewind !== $i) {continue;}$r = $token;$injector->handleEnd($r);$token = $this->processToken($r, $i);$this->stack[] = $current_parent;$reprocess = true;break;}continue;}// okay, so we're trying to close the wrong tag// undo the pop previous pop$this->stack[] = $current_parent;// scroll back the entire nest, trying to find our tag.// (feature could be to specify how far you'd like to go)$size = count($this->stack);// -2 because -1 is the last element, but we already checked that$skipped_tags = false;for ($j = $size - 2; $j >= 0; $j--) {if ($this->stack[$j]->name == $token->name) {$skipped_tags = array_slice($this->stack, $j);break;}}// we didn't find the tag, so removeif ($skipped_tags === false) {if ($escape_invalid_tags) {if ($e) {$e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');}$token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));} else {if ($e) {$e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');}$token = $this->remove();}$reprocess = true;continue;}// do errors, in REVERSE $j order: a,b,c with </a></b></c>$c = count($skipped_tags);if ($e) {for ($j = $c - 1; $j > 0; $j--) {// notice we exclude $j == 0, i.e. the current ending tag, from// the errors... [TagClosedSuppress]if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {$e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);}}}// insert tags, in FORWARD $j order: c,b,a with </a></b></c>$replace = array($token);for ($j = 1; $j < $c; $j++) {// ...as well as from the insertions$new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);$new_token->start = $skipped_tags[$j];array_unshift($replace, $new_token);if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {// [TagClosedAuto]$element = clone $skipped_tags[$j];$element->carryover = true;$element->armor['MakeWellFormed_TagClosedError'] = true;$replace[] = $element;}}$token = $this->processToken($replace);$reprocess = true;continue;}$context->destroy('CurrentToken');$context->destroy('CurrentNesting');$context->destroy('InputZipper');unset($this->injectors, $this->stack, $this->tokens);return $zipper->toArray($token);}/*** Processes arbitrary token values for complicated substitution patterns.* In general:** If $token is an array, it is a list of tokens to substitute for the* current token. These tokens then get individually processed. If there* is a leading integer in the list, that integer determines how many* tokens from the stream should be removed.** If $token is a regular token, it is swapped with the current token.** If $token is false, the current token is deleted.** If $token is an integer, that number of tokens (with the first token* being the current one) will be deleted.** @param HTMLPurifier_Token|array|int|bool $token Token substitution value* @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if* this is not an injector related operation.* @throws HTMLPurifier_Exception*/protected function processToken($token, $injector = -1){// Zend OpCache miscompiles $token = array($token), so// avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108// normalize forms of tokenif (is_object($token)) {$tmp = $token;$token = array(1, $tmp);}if (is_int($token)) {$tmp = $token;$token = array($tmp);}if ($token === false) {$token = array(1);}if (!is_array($token)) {throw new HTMLPurifier_Exception('Invalid token type from injector');}if (!is_int($token[0])) {array_unshift($token, 1);}if ($token[0] === 0) {throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');}// $token is now an array with the following form:// array(number nodes to delete, new node 1, new node 2, ...)$delete = array_shift($token);list($old, $r) = $this->zipper->splice($this->token, $delete, $token);if ($injector > -1) {// See Note [Injector skips]// Determine appropriate skips. Here's what the code does:// *If* we deleted one or more tokens, copy the skips// of those tokens into the skips of the new tokens (in $token).// Also, mark the newly inserted tokens as having come from// $injector.$oldskip = isset($old[0]) ? $old[0]->skip : array();foreach ($token as $object) {$object->skip = $oldskip;$object->skip[$injector] = true;}}return $r;}/*** Inserts a token before the current token. Cursor now points to* this token. You must reprocess after this.* @param HTMLPurifier_Token $token*/private function insertBefore($token){// NB not $this->zipper->insertBefore(), due to positioning// differences$splice = $this->zipper->splice($this->token, 0, array($token));return $splice[1];}/*** Removes current token. Cursor now points to new token occupying previously* occupied space. You must reprocess after this.*/private function remove(){return $this->zipper->delete();}}// Note [Injector skips]// ~~~~~~~~~~~~~~~~~~~~~// When I originally designed this class, the idea behind the 'skip'// property of HTMLPurifier_Token was to help avoid infinite loops// in injector processing. For example, suppose you wrote an injector// that bolded swear words. Naively, you might write it so that// whenever you saw ****, you replaced it with <strong>****</strong>.//// When this happens, we will reprocess all of the tokens with the// other injectors. Now there is an opportunity for infinite loop:// if we rerun the swear-word injector on these tokens, we might// see **** and then reprocess again to get// <strong><strong>****</strong></strong> ad infinitum.//// Thus, the idea of a skip is that once we process a token with// an injector, we mark all of those tokens as having "come from"// the injector, and we never run the injector again on these// tokens.//// There were two more complications, however://// - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if// you had <b><i></i></b>, after you removed the <i></i>, you// really would like this injector to go back and reprocess// the <b> tag, discovering that it is now empty and can be// removed. So we reintroduced the possibility of infinite looping// by adding a "rewind" function, which let you go back to an// earlier point in the token stream and reprocess it with injectors.// Needless to say, we need to UN-skip the token so it gets// reprocessed.//// - Suppose that you successfuly process a token, replace it with// one with your skip mark, but now another injector wants to// process the skipped token with another token. Should you continue// to skip that new token, or reprocess it? If you reprocess,// you can end up with an infinite loop where one injector converts// <a> to <b>, and then another injector converts it back. So// we inherit the skips, but for some reason, I thought that we// should inherit the skip from the first token of the token// that we deleted. Why? Well, it seems to work OK.//// If I were to redesign this functionality, I would absolutely not// go about doing it this way: the semantics are just not very well// defined, and in any case you probably wanted to operate on trees,// not token streams./*** Removes all unrecognized tags from the list of tokens.** This strategy iterates through all the tokens and removes unrecognized* tokens. If a token is not recognized but a TagTransform is defined for* that element, the element will be transformed accordingly.*/class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy{/*** @param HTMLPurifier_Token[] $tokens* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return array|HTMLPurifier_Token[]*/public function execute($tokens, $config, $context){$definition = $config->getHTMLDefinition();$generator = new HTMLPurifier_Generator($config, $context);$result = array();$escape_invalid_tags = $config->get('Core.EscapeInvalidTags');$remove_invalid_img = $config->get('Core.RemoveInvalidImg');// currently only used to determine if comments should be kept$trusted = $config->get('HTML.Trusted');$comment_lookup = $config->get('HTML.AllowedComments');$comment_regexp = $config->get('HTML.AllowedCommentsRegexp');$check_comments = $comment_lookup !== array() || $comment_regexp !== null;$remove_script_contents = $config->get('Core.RemoveScriptContents');$hidden_elements = $config->get('Core.HiddenElements');// remove script contents compatibilityif ($remove_script_contents === true) {$hidden_elements['script'] = true;} elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {unset($hidden_elements['script']);}$attr_validator = new HTMLPurifier_AttrValidator();// removes tokens until it reaches a closing tag with its value$remove_until = false;// converts comments into text tokens when this is equal to a tag name$textify_comments = false;$token = false;$context->register('CurrentToken', $token);$e = false;if ($config->get('Core.CollectErrors')) {$e =& $context->get('ErrorCollector');}foreach ($tokens as $token) {if ($remove_until) {if (empty($token->is_tag) || $token->name !== $remove_until) {continue;}}if (!empty($token->is_tag)) {// DEFINITION CALL// before any processing, try to transform the elementif (isset($definition->info_tag_transform[$token->name])) {$original_name = $token->name;// there is a transformation for this tag// DEFINITION CALL$token = $definition->info_tag_transform[$token->name]->transform($token, $config, $context);if ($e) {$e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);}}if (isset($definition->info[$token->name])) {// mostly everything's good, but// we need to make sure required attributes are in orderif (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&$definition->info[$token->name]->required_attr &&($token->name != 'img' || $remove_invalid_img) // ensure config option still works) {$attr_validator->validateToken($token, $config, $context);$ok = true;foreach ($definition->info[$token->name]->required_attr as $name) {if (!isset($token->attr[$name])) {$ok = false;break;}}if (!$ok) {if ($e) {$e->send(E_ERROR,'Strategy_RemoveForeignElements: Missing required attribute',$name);}continue;}$token->armor['ValidateAttributes'] = true;}if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {$textify_comments = $token->name;} elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {$textify_comments = false;}} elseif ($escape_invalid_tags) {// invalid tag, generate HTML representation and insert inif ($e) {$e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');}$token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));} else {// check if we need to destroy all of the tag's children// CAN BE GENERICIZEDif (isset($hidden_elements[$token->name])) {if ($token instanceof HTMLPurifier_Token_Start) {$remove_until = $token->name;} elseif ($token instanceof HTMLPurifier_Token_Empty) {// do nothing: we're still looking} else {$remove_until = false;}if ($e) {$e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');}} else {if ($e) {$e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');}}continue;}} elseif ($token instanceof HTMLPurifier_Token_Comment) {// textify comments in script tags when they are allowedif ($textify_comments !== false) {$data = $token->data;$token = new HTMLPurifier_Token_Text($data);} elseif ($trusted || $check_comments) {// always cleanup comments$trailing_hyphen = false;if ($e) {// perform check whether or not there's a trailing hyphenif (substr($token->data, -1) == '-') {$trailing_hyphen = true;}}$token->data = rtrim($token->data, '-');$found_double_hyphen = false;while (strpos($token->data, '--') !== false) {$found_double_hyphen = true;$token->data = str_replace('--', '-', $token->data);}if ($trusted || !empty($comment_lookup[trim($token->data)]) ||($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {// OK goodif ($e) {if ($trailing_hyphen) {$e->send(E_NOTICE,'Strategy_RemoveForeignElements: Trailing hyphen in comment removed');}if ($found_double_hyphen) {$e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');}}} else {if ($e) {$e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');}continue;}} else {// strip commentsif ($e) {$e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');}continue;}} elseif ($token instanceof HTMLPurifier_Token_Text) {} else {continue;}$result[] = $token;}if ($remove_until && $e) {// we removed tokens until the end, throw error$e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);}$context->destroy('CurrentToken');return $result;}}/*** Validate all attributes in the tokens.*/class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy{/*** @param HTMLPurifier_Token[] $tokens* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_Token[]*/public function execute($tokens, $config, $context){// setup validator$validator = new HTMLPurifier_AttrValidator();$token = false;$context->register('CurrentToken', $token);foreach ($tokens as $key => $token) {// only process tokens that have attributes,// namely start and empty tagsif (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {continue;}// skip tokens that are armoredif (!empty($token->armor['ValidateAttributes'])) {continue;}// note that we have no facilities here for removing tokens$validator->validateToken($token, $config, $context);}$context->destroy('CurrentToken');return $tokens;}}/*** Transforms FONT tags to the proper form (SPAN with CSS styling)** This transformation takes the three proprietary attributes of FONT and* transforms them into their corresponding CSS attributes. These are color,* face, and size.** @note Size is an interesting case because it doesn't map cleanly to CSS.* Thanks to* http://style.cleverchimp.com/font_size_intervals/altintervals.html* for reasonable mappings.* @warning This doesn't work completely correctly; specifically, this* TagTransform operates before well-formedness is enforced, so* the "active formatting elements" algorithm doesn't get applied.*/class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform{/*** @type string*/public $transform_to = 'span';/*** @type array*/protected $_size_lookup = array('0' => 'xx-small','1' => 'xx-small','2' => 'small','3' => 'medium','4' => 'large','5' => 'x-large','6' => 'xx-large','7' => '300%','-1' => 'smaller','-2' => '60%','+1' => 'larger','+2' => '150%','+3' => '200%','+4' => '300%');/*** @param HTMLPurifier_Token_Tag $tag* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return HTMLPurifier_Token_End|string*/public function transform($tag, $config, $context){if ($tag instanceof HTMLPurifier_Token_End) {$new_tag = clone $tag;$new_tag->name = $this->transform_to;return $new_tag;}$attr = $tag->attr;$prepend_style = '';// handle color transformif (isset($attr['color'])) {$prepend_style .= 'color:' . $attr['color'] . ';';unset($attr['color']);}// handle face transformif (isset($attr['face'])) {$prepend_style .= 'font-family:' . $attr['face'] . ';';unset($attr['face']);}// handle size transformif (isset($attr['size'])) {// normalize large numbersif ($attr['size'] !== '') {if ($attr['size'][0] == '+' || $attr['size'][0] == '-') {$size = (int)$attr['size'];if ($size < -2) {$attr['size'] = '-2';}if ($size > 4) {$attr['size'] = '+4';}} else {$size = (int)$attr['size'];if ($size > 7) {$attr['size'] = '7';}}}if (isset($this->_size_lookup[$attr['size']])) {$prepend_style .= 'font-size:' .$this->_size_lookup[$attr['size']] . ';';}unset($attr['size']);}if ($prepend_style) {$attr['style'] = isset($attr['style']) ?$prepend_style . $attr['style'] :$prepend_style;}$new_tag = clone $tag;$new_tag->name = $this->transform_to;$new_tag->attr = $attr;return $new_tag;}}/*** Simple transformation, just change tag name to something else,* and possibly add some styling. This will cover most of the deprecated* tag cases.*/class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform{/*** @type string*/protected $style;/*** @param string $transform_to Tag name to transform to.* @param string $style CSS style to add to the tag*/public function __construct($transform_to, $style = null){$this->transform_to = $transform_to;$this->style = $style;}/*** @param HTMLPurifier_Token_Tag $tag* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return string*/public function transform($tag, $config, $context){$new_tag = clone $tag;$new_tag->name = $this->transform_to;if (!is_null($this->style) &&($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)) {$this->prependCSS($new_tag->attr, $this->style);}return $new_tag;}}/*** Concrete comment token class. Generally will be ignored.*/class HTMLPurifier_Token_Comment extends HTMLPurifier_Token{/*** Character data within comment.* @type string*/public $data;/*** @type bool*/public $is_whitespace = true;/*** Transparent constructor.** @param string $data String comment data.* @param int $line* @param int $col*/public function __construct($data, $line = null, $col = null){$this->data = $data;$this->line = $line;$this->col = $col;}public function toNode() {return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);}}/*** Abstract class of a tag token (start, end or empty), and its behavior.*/abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token{/*** Static bool marker that indicates the class is a tag.** This allows us to check objects with <tt>!empty($obj->is_tag)</tt>* without having to use a function call <tt>is_a()</tt>.* @type bool*/public $is_tag = true;/*** The lower-case name of the tag, like 'a', 'b' or 'blockquote'.** @note Strictly speaking, XML tags are case sensitive, so we shouldn't* be lower-casing them, but these tokens cater to HTML tags, which are* insensitive.* @type string*/public $name;/*** Associative array of the tag's attributes.* @type array*/public $attr = array();/*** Non-overloaded constructor, which lower-cases passed tag name.** @param string $name String name.* @param array $attr Associative array of attributes.* @param int $line* @param int $col* @param array $armor*/public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()){$this->name = ctype_lower($name) ? $name : strtolower($name);foreach ($attr as $key => $value) {// normalization only necessary when key is not lowercaseif (!ctype_lower($key)) {$new_key = strtolower($key);if (!isset($attr[$new_key])) {$attr[$new_key] = $attr[$key];}if ($new_key !== $key) {unset($attr[$key]);}}}$this->attr = $attr;$this->line = $line;$this->col = $col;$this->armor = $armor;}public function toNode() {return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);}}/*** Concrete empty token class.*/class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag{public function toNode() {$n = parent::toNode();$n->empty = true;return $n;}}/*** Concrete end token class.** @warning This class accepts attributes even though end tags cannot. This* is for optimization reasons, as under normal circumstances, the Lexers* do not pass attributes.*/class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag{/*** Token that started this node.* Added by MakeWellFormed. Please do not edit this!* @type HTMLPurifier_Token*/public $start;public function toNode() {throw new Exception("HTMLPurifier_Token_End->toNode not supported!");}}/*** Concrete start token class.*/class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag{}/*** Concrete text token class.** Text tokens comprise of regular parsed character data (PCDATA) and raw* character data (from the CDATA sections). Internally, their* data is parsed with all entities expanded. Surprisingly, the text token* does have a "tag name" called #PCDATA, which is how the DTD represents it* in permissible child nodes.*/class HTMLPurifier_Token_Text extends HTMLPurifier_Token{/*** @type string*/public $name = '#PCDATA';/**< PCDATA tag name compatible with DTD. *//*** @type string*/public $data;/**< Parsed character data of text. *//*** @type bool*/public $is_whitespace;/**< Bool indicating if node is whitespace. *//*** Constructor, accepts data and determines if it is whitespace.* @param string $data String parsed character data.* @param int $line* @param int $col*/public function __construct($data, $line = null, $col = null){$this->data = $data;$this->is_whitespace = ctype_space($data);$this->line = $line;$this->col = $col;}public function toNode() {return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);}}class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter{/*** @type string*/public $name = 'DisableExternal';/*** @type array*/protected $ourHostParts = false;/*** @param HTMLPurifier_Config $config* @return void*/public function prepare($config){$our_host = $config->getDefinition('URI')->host;if ($our_host !== null) {$this->ourHostParts = array_reverse(explode('.', $our_host));}}/*** @param HTMLPurifier_URI $uri Reference* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function filter(&$uri, $config, $context){if (is_null($uri->host)) {return true;}if ($this->ourHostParts === false) {return false;}$host_parts = array_reverse(explode('.', $uri->host));foreach ($this->ourHostParts as $i => $x) {if (!isset($host_parts[$i])) {return false;}if ($host_parts[$i] != $this->ourHostParts[$i]) {return false;}}return true;}}class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal{/*** @type string*/public $name = 'DisableExternalResources';/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function filter(&$uri, $config, $context){if (!$context->get('EmbeddedURI', true)) {return true;}return parent::filter($uri, $config, $context);}}class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter{/*** @type string*/public $name = 'DisableResources';/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function filter(&$uri, $config, $context){return !$context->get('EmbeddedURI', true);}}// It's not clear to me whether or not Punycode means that hostnames// do not have canonical forms anymore. As far as I can tell, it's// not a problem (punycoding should be identity when no Unicode// points are involved), but I'm not 100% sureclass HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter{/*** @type string*/public $name = 'HostBlacklist';/*** @type array*/protected $blacklist = array();/*** @param HTMLPurifier_Config $config* @return bool*/public function prepare($config){$this->blacklist = $config->get('URI.HostBlacklist');return true;}/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function filter(&$uri, $config, $context){foreach ($this->blacklist as $blacklisted_host_fragment) {if (strpos($uri->host, $blacklisted_host_fragment) !== false) {return false;}}return true;}}// does not support network pathsclass HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter{/*** @type string*/public $name = 'MakeAbsolute';/*** @type*/protected $base;/*** @type array*/protected $basePathStack = array();/*** @param HTMLPurifier_Config $config* @return bool*/public function prepare($config){$def = $config->getDefinition('URI');$this->base = $def->base;if (is_null($this->base)) {trigger_error('URI.MakeAbsolute is being ignored due to lack of ' .'value for URI.Base configuration',E_USER_WARNING);return false;}$this->base->fragment = null; // fragment is invalid for base URI$stack = explode('/', $this->base->path);array_pop($stack); // discard last segment$stack = $this->_collapseStack($stack); // do pre-parsing$this->basePathStack = $stack;return true;}/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function filter(&$uri, $config, $context){if (is_null($this->base)) {return true;} // abort earlyif ($uri->path === '' && is_null($uri->scheme) &&is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {// reference to current document$uri = clone $this->base;return true;}if (!is_null($uri->scheme)) {// absolute URI already: don't changeif (!is_null($uri->host)) {return true;}$scheme_obj = $uri->getSchemeObj($config, $context);if (!$scheme_obj) {// scheme not recognizedreturn false;}if (!$scheme_obj->hierarchical) {// non-hierarchal URI with explicit scheme, don't changereturn true;}// special case: had a scheme but always is hierarchical and had no authority}if (!is_null($uri->host)) {// network path, don't botherreturn true;}if ($uri->path === '') {$uri->path = $this->base->path;} elseif ($uri->path[0] !== '/') {// relative path, needs more complicated processing$stack = explode('/', $uri->path);$new_stack = array_merge($this->basePathStack, $stack);if ($new_stack[0] !== '' && !is_null($this->base->host)) {array_unshift($new_stack, '');}$new_stack = $this->_collapseStack($new_stack);$uri->path = implode('/', $new_stack);} else {// absolute path, but still we should collapse$uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));}// re-combine$uri->scheme = $this->base->scheme;if (is_null($uri->userinfo)) {$uri->userinfo = $this->base->userinfo;}if (is_null($uri->host)) {$uri->host = $this->base->host;}if (is_null($uri->port)) {$uri->port = $this->base->port;}return true;}/*** Resolve dots and double-dots in a path stack* @param array $stack* @return array*/private function _collapseStack($stack){$result = array();$is_folder = false;for ($i = 0; isset($stack[$i]); $i++) {$is_folder = false;// absorb an internally duplicated slashif ($stack[$i] == '' && $i && isset($stack[$i + 1])) {continue;}if ($stack[$i] == '..') {if (!empty($result)) {$segment = array_pop($result);if ($segment === '' && empty($result)) {// error case: attempted to back out too far:// restore the leading slash$result[] = '';} elseif ($segment === '..') {$result[] = '..'; // cannot remove .. with ..}} else {// relative path, preserve the double-dots$result[] = '..';}$is_folder = true;continue;}if ($stack[$i] == '.') {// silently absorb$is_folder = true;continue;}$result[] = $stack[$i];}if ($is_folder) {$result[] = '';}return $result;}}class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter{/*** @type string*/public $name = 'Munge';/*** @type bool*/public $post = true;/*** @type string*/private $target;/*** @type HTMLPurifier_URIParser*/private $parser;/*** @type bool*/private $doEmbed;/*** @type string*/private $secretKey;/*** @type array*/protected $replace = array();/*** @param HTMLPurifier_Config $config* @return bool*/public function prepare($config){$this->target = $config->get('URI.' . $this->name);$this->parser = new HTMLPurifier_URIParser();$this->doEmbed = $config->get('URI.MungeResources');$this->secretKey = $config->get('URI.MungeSecretKey');if ($this->secretKey && !function_exists('hash_hmac')) {throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");}return true;}/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function filter(&$uri, $config, $context){if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {return true;}$scheme_obj = $uri->getSchemeObj($config, $context);if (!$scheme_obj) {return true;} // ignore unknown schemes, maybe another postfilter did itif (!$scheme_obj->browsable) {return true;} // ignore non-browseable schemes, since we can't munge those in a reasonable wayif ($uri->isBenign($config, $context)) {return true;} // don't redirect if a benign URL$this->makeReplace($uri, $config, $context);$this->replace = array_map('rawurlencode', $this->replace);$new_uri = strtr($this->target, $this->replace);$new_uri = $this->parser->parse($new_uri);// don't redirect if the target host is the same as the// starting hostif ($uri->host === $new_uri->host) {return true;}$uri = $new_uri; // overwritereturn true;}/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context*/protected function makeReplace($uri, $config, $context){$string = $uri->toString();// always available$this->replace['%s'] = $string;$this->replace['%r'] = $context->get('EmbeddedURI', true);$token = $context->get('CurrentToken', true);$this->replace['%n'] = $token ? $token->name : null;$this->replace['%m'] = $context->get('CurrentAttr', true);$this->replace['%p'] = $context->get('CurrentCSSProperty', true);// not always availableif ($this->secretKey) {$this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);}}}/*** Implements safety checks for safe iframes.** @warning This filter is *critical* for ensuring that %HTML.SafeIframe* works safely.*/class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter{/*** @type string*/public $name = 'SafeIframe';/*** @type bool*/public $always_load = true;/*** @type string*/protected $regexp = null;// XXX: The not so good bit about how this is all set up now is we// can't check HTML.SafeIframe in the 'prepare' step: we have to// defer till the actual filtering./*** @param HTMLPurifier_Config $config* @return bool*/public function prepare($config){$this->regexp = $config->get('URI.SafeIframeRegexp');return true;}/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function filter(&$uri, $config, $context){// check if filter not applicableif (!$config->get('HTML.SafeIframe')) {return true;}// check if the filter should actually triggerif (!$context->get('EmbeddedURI', true)) {return true;}$token = $context->get('CurrentToken', true);if (!($token && $token->name == 'iframe')) {return true;}// check if we actually have some whitelists enabledif ($this->regexp === null) {return false;}// actually check the whitelistsreturn preg_match($this->regexp, $uri->toString());}}/*** Implements data: URI for base64 encoded images supported by GD.*/class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme{/*** @type bool*/public $browsable = true;/*** @type array*/public $allowed_types = array(// you better write validation code for other types if you// decide to allow them'image/jpeg' => true,'image/gif' => true,'image/png' => true,);// this is actually irrelevant since we only write out the path// component/*** @type bool*/public $may_omit_host = true;/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function doValidate(&$uri, $config, $context){$result = explode(',', $uri->path, 2);$is_base64 = false;$charset = null;$content_type = null;if (count($result) == 2) {list($metadata, $data) = $result;// do some legwork on the metadata$metas = explode(';', $metadata);while (!empty($metas)) {$cur = array_shift($metas);if ($cur == 'base64') {$is_base64 = true;break;}if (substr($cur, 0, 8) == 'charset=') {// doesn't match if there are arbitrary spaces, but// whatever dudeif ($charset !== null) {continue;} // garbage$charset = substr($cur, 8); // not used} else {if ($content_type !== null) {continue;} // garbage$content_type = $cur;}}} else {$data = $result[0];}if ($content_type !== null && empty($this->allowed_types[$content_type])) {return false;}if ($charset !== null) {// error; we don't allow plaintext stuff$charset = null;}$data = rawurldecode($data);if ($is_base64) {$raw_data = base64_decode($data);} else {$raw_data = $data;}if ( strlen($raw_data) < 12 ) {// error; exif_imagetype throws exception with small files,// and this likely indicates a corrupt URI/failed parse anywayreturn false;}// XXX probably want to refactor this into a general mechanism// for filtering arbitrary content typesif (function_exists('sys_get_temp_dir')) {$file = tempnam(sys_get_temp_dir(), "");} else {$file = tempnam("/tmp", "");}file_put_contents($file, $raw_data);if (function_exists('exif_imagetype')) {$image_code = exif_imagetype($file);unlink($file);} elseif (function_exists('getimagesize')) {set_error_handler(array($this, 'muteErrorHandler'));$info = getimagesize($file);restore_error_handler();unlink($file);if ($info == false) {return false;}$image_code = $info[2];} else {trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);}$real_content_type = image_type_to_mime_type($image_code);if ($real_content_type != $content_type) {// we're nice guys; if the content type is something else we// support, change it overif (empty($this->allowed_types[$real_content_type])) {return false;}$content_type = $real_content_type;}// ok, it's kosher, rewrite what we need$uri->userinfo = null;$uri->host = null;$uri->port = null;$uri->fragment = null;$uri->query = null;$uri->path = "$content_type;base64," . base64_encode($raw_data);return true;}/*** @param int $errno* @param string $errstr*/public function muteErrorHandler($errno, $errstr){}}/*** Validates file as defined by RFC 1630 and RFC 1738.*/class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme{/*** Generally file:// URLs are not accessible from most* machines, so placing them as an img src is incorrect.* @type bool*/public $browsable = false;/*** Basically the *only* URI scheme for which this is true, since* accessing files on the local machine is very common. In fact,* browsers on some operating systems don't understand the* authority, though I hear it is used on Windows to refer to* network shares.* @type bool*/public $may_omit_host = true;/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function doValidate(&$uri, $config, $context){// Authentication method is not supported$uri->userinfo = null;// file:// makes no provisions for accessing the resource$uri->port = null;// While it seems to work on Firefox, the querystring has// no possible effect and is thus stripped.$uri->query = null;return true;}}/*** Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.*/class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme{/*** @type int*/public $default_port = 21;/*** @type bool*/public $browsable = true; // usually/*** @type bool*/public $hierarchical = true;/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function doValidate(&$uri, $config, $context){$uri->query = null;// typecode check$semicolon_pos = strrpos($uri->path, ';'); // reverseif ($semicolon_pos !== false) {$type = substr($uri->path, $semicolon_pos + 1); // no semicolon$uri->path = substr($uri->path, 0, $semicolon_pos);$type_ret = '';if (strpos($type, '=') !== false) {// figure out whether or not the declaration is correctlist($key, $typecode) = explode('=', $type, 2);if ($key !== 'type') {// invalid key, tack it back on encoded$uri->path .= '%3B' . $type;} elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {$type_ret = ";type=$typecode";}} else {$uri->path .= '%3B' . $type;}$uri->path = str_replace(';', '%3B', $uri->path);$uri->path .= $type_ret;}return true;}}/*** Validates http (HyperText Transfer Protocol) as defined by RFC 2616*/class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme{/*** @type int*/public $default_port = 80;/*** @type bool*/public $browsable = true;/*** @type bool*/public $hierarchical = true;/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function doValidate(&$uri, $config, $context){$uri->userinfo = null;return true;}}/*** Validates https (Secure HTTP) according to http scheme.*/class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http{/*** @type int*/public $default_port = 443;/*** @type bool*/public $secure = true;}// VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the// email is valid, but be careful!/*** Validates mailto (for E-mail) according to RFC 2368* @todo Validate the email address* @todo Filter allowed query parameters*/class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme{/*** @type bool*/public $browsable = false;/*** @type bool*/public $may_omit_host = true;/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function doValidate(&$uri, $config, $context){$uri->userinfo = null;$uri->host = null;$uri->port = null;// we need to validate path against RFC 2368's addr-specreturn true;}}/*** Validates news (Usenet) as defined by generic RFC 1738*/class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme{/*** @type bool*/public $browsable = false;/*** @type bool*/public $may_omit_host = true;/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function doValidate(&$uri, $config, $context){$uri->userinfo = null;$uri->host = null;$uri->port = null;$uri->query = null;// typecode check needed on pathreturn true;}}/*** Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738*/class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme{/*** @type int*/public $default_port = 119;/*** @type bool*/public $browsable = false;/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function doValidate(&$uri, $config, $context){$uri->userinfo = null;$uri->query = null;return true;}}/*** Validates tel (for phone numbers).** The relevant specifications for this protocol are RFC 3966 and RFC 5341,* but this class takes a much simpler approach: we normalize phone* numbers so that they only include (possibly) a leading plus,* and then any number of digits and x'es.*/class HTMLPurifier_URIScheme_tel extends HTMLPurifier_URIScheme{/*** @type bool*/public $browsable = false;/*** @type bool*/public $may_omit_host = true;/*** @param HTMLPurifier_URI $uri* @param HTMLPurifier_Config $config* @param HTMLPurifier_Context $context* @return bool*/public function doValidate(&$uri, $config, $context){$uri->userinfo = null;$uri->host = null;$uri->port = null;// Delete all non-numeric characters, non-x characters// from phone number, EXCEPT for a leading plus sign.$uri->path = preg_replace('/(?!^\+)[^\dx]/', '',// Normalize e(x)tension to lower-casestr_replace('X', 'x', $uri->path));return true;}}/*** Performs safe variable parsing based on types which can be used by* users. This may not be able to represent all possible data inputs,* however.*/class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser{/*** @param mixed $var* @param int $type* @param bool $allow_null* @return array|bool|float|int|mixed|null|string* @throws HTMLPurifier_VarParserException*/protected function parseImplementation($var, $type, $allow_null){if ($allow_null && $var === null) {return null;}switch ($type) {// Note: if code "breaks" from the switch, it triggers a generic// exception to be thrown. Specific errors can be specifically// done here.case self::C_MIXED:case self::ISTRING:case self::C_STRING:case self::TEXT:case self::ITEXT:return $var;case self::C_INT:if (is_string($var) && ctype_digit($var)) {$var = (int)$var;}return $var;case self::C_FLOAT:if ((is_string($var) && is_numeric($var)) || is_int($var)) {$var = (float)$var;}return $var;case self::C_BOOL:if (is_int($var) && ($var === 0 || $var === 1)) {$var = (bool)$var;} elseif (is_string($var)) {if ($var == 'on' || $var == 'true' || $var == '1') {$var = true;} elseif ($var == 'off' || $var == 'false' || $var == '0') {$var = false;} else {throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");}}return $var;case self::ALIST:case self::HASH:case self::LOOKUP:if (is_string($var)) {// special case: technically, this is an array with// a single empty string item, but having an empty// array is more intuitiveif ($var == '') {return array();}if (strpos($var, "\n") === false && strpos($var, "\r") === false) {// simplistic string to array method that only works// for simple lists of tag names or alphanumeric characters$var = explode(',', $var);} else {$var = preg_split('/(,|[\n\r]+)/', $var);}// remove spacesforeach ($var as $i => $j) {$var[$i] = trim($j);}if ($type === self::HASH) {// key:value,key2:value2$nvar = array();foreach ($var as $keypair) {$c = explode(':', $keypair, 2);if (!isset($c[1])) {continue;}$nvar[trim($c[0])] = trim($c[1]);}$var = $nvar;}}if (!is_array($var)) {break;}$keys = array_keys($var);if ($keys === array_keys($keys)) {if ($type == self::ALIST) {return $var;} elseif ($type == self::LOOKUP) {$new = array();foreach ($var as $key) {$new[$key] = true;}return $new;} else {break;}}if ($type === self::ALIST) {trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);return array_values($var);}if ($type === self::LOOKUP) {foreach ($var as $key => $value) {if ($value !== true) {trigger_error("Lookup array has non-true value at key '$key'; " ."maybe your input array was not indexed numerically",E_USER_WARNING);}$var[$key] = true;}}return $var;default:$this->errorInconsistent(__CLASS__, $type);}$this->errorGeneric($var, $type);}}/*** This variable parser uses PHP's internal code engine. Because it does* this, it can represent all inputs; however, it is dangerous and cannot* be used by users.*/class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser{/*** @param mixed $var* @param int $type* @param bool $allow_null* @return null|string*/protected function parseImplementation($var, $type, $allow_null){return $this->evalExpression($var);}/*** @param string $expr* @return mixed* @throws HTMLPurifier_VarParserException*/protected function evalExpression($expr){$var = null;$result = eval("\$var = $expr;");if ($result === false) {throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");}return $var;}}