99 private $filters = array();
105 private static $instance;
148 'HTMLPurifier->addFilter() is deprecated, use configuration directives' .
149 ' in the Filter namespace or Filter.Custom',
152 $this->filters[] = $filter;
179 $context->register(
'Generator', $this->generator);
182 if (
$config->get(
'Core.CollectErrors')) {
186 $context->register(
'Locale', $language);
189 $context->register(
'ErrorCollector', $error_collector);
195 $context->register(
'IDAccumulator', $id_accumulator);
200 $filter_flags =
$config->getBatch(
'Filter');
201 $custom_filters = $filter_flags[
'Custom'];
202 unset($filter_flags[
'Custom']);
204 foreach ($filter_flags
as $filter => $flag) {
208 if (strpos($filter,
'.') !==
false) {
211 $class =
"HTMLPurifier_Filter_$filter";
212 $filters[] =
new $class;
214 foreach ($custom_filters
as $filter) {
216 $filters[] = $filter;
218 $filters = array_merge($filters, $this->filters);
221 for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) {
227 $this->generator->generateFromTokens(
229 $this->strategy->execute(
231 $lexer->tokenizeHTML(
242 for ($i = $filter_size - 1; $i >= 0; $i--) {
262 $context_array = array();
263 foreach($array_of_html
as $key=>$value){
264 if (is_array($value)) {
271 $this->
context = $context_array;
287 if (!self::$instance || $prototype) {
289 self::$instance = $prototype;
290 } elseif ($prototype) {
296 return self::$instance;
330 $definition =
$config->getHTMLDefinition();
332 $stack = array($parent->toNode());
333 foreach ($tokens
as $token) {
335 $token->carryover =
null;
337 $token->start =
null;
338 $r = array_pop($stack);
341 $r->endCol = $token->col;
342 $r->endLine = $token->line;
343 $r->endArmor = $token->armor;
346 $node = $token->toNode();
347 $stack[count($stack)-1]->children[] = $node;
359 $closingTokens = array();
362 while (!$nodes[$level]->isEmpty()) {
363 $node = $nodes[$level]->shift();
364 list($start, $end) = $node->toTokenPair();
369 $closingTokens[$level][] = $end;
374 foreach ($node->children
as $childNode) {
375 $nodes[$level]->push($childNode);
380 if ($level && isset($closingTokens[$level])) {
381 while ($token = array_pop($closingTokens[$level])) {
385 }
while ($level > 0);
420 foreach ($modules
as $module) {
421 foreach ($module->attr_collections
as $coll_i => $coll) {
422 if (!isset($this->info[$coll_i])) {
423 $this->info[$coll_i] = array();
425 foreach ($coll
as $attr_i => $attr) {
426 if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
428 $this->info[$coll_i][$attr_i] = array_merge(
429 $this->info[$coll_i][$attr_i],
434 $this->info[$coll_i][$attr_i] = $attr;
439 foreach ($this->info
as $name => $attr) {
454 if (!isset($attr[0])) {
460 for ($i = 0; isset($merge[$i]); $i++) {
461 if (isset($seen[$merge[$i]])) {
464 $seen[$merge[$i]] =
true;
466 if (!isset($this->info[$merge[$i]])) {
469 foreach ($this->info[$merge[$i]]
as $key => $value) {
470 if (isset($attr[$key])) {
473 $attr[$key] = $value;
475 if (isset($this->info[$merge[$i]][0])) {
477 $merge = array_merge($merge, $this->info[$merge[$i]][0]);
493 $processed = array();
495 foreach ($attr
as $def_i =>
$def) {
501 if (isset($processed[$def_i])) {
506 if ($required = (strpos($def_i,
'*') !==
false)) {
508 unset($attr[$def_i]);
509 $def_i = trim($def_i,
'*');
510 $attr[$def_i] =
$def;
513 $processed[$def_i] =
true;
516 if (is_object(
$def)) {
518 $attr[$def_i]->required = ($required || $attr[$def_i]->required);
522 if (
$def ===
false) {
523 unset($attr[$def_i]);
527 if ($t = $attr_types->get(
$def)) {
529 $attr[$def_i]->required = $required;
531 unset($attr[$def_i]);
600 $string = trim($string);
601 $string = str_replace(array(
"\n",
"\t",
"\r"),
' ', $string);
627 $p =
'\s*(\d+(\.\d+)?([%]?))\s*';
629 if (preg_match(
'/(rgba|hsla)\(/', $string)) {
630 return preg_replace(
'/(rgba|hsla)\('.$p.
','.$p.
','.$p.
','.$p.
'\)/',
'\1(\2,\5,\8,\11)', $string);
633 return preg_replace(
'/(rgb|hsl)\('.$p.
','.$p.
','.$p.
'\)/',
'\1(\2,\5,\8)', $string);
644 for ($i = 0, $c = strlen($string); $i < $c; $i++) {
645 if ($string[$i] ===
'\\') {
651 if (ctype_xdigit($string[$i])) {
653 for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
654 if (!ctype_xdigit($string[$i])) {
657 $code .= $string[$i];
667 if ($i < $c && trim($string[$i]) !==
'') {
672 if ($string[$i] ===
"\n") {
722 $attr[
'style'] = isset($attr[
'style']) ? $attr[
'style'] :
'';
723 $attr[
'style'] = $css . $attr[
'style'];
734 if (!isset($attr[$key])) {
737 $value = $attr[$key];
785 $this->info[
'IAlign'] = self::makeEnum(
'top,middle,bottom,left,right');
786 $this->info[
'LAlign'] = self::makeEnum(
'top,bottom,left,right');
803 private static function makeEnum($in)
813 public function get($type)
816 if (strpos($type,
'#') !==
false) {
817 list($type, $string) = explode(
'#', $type, 2);
822 if (!isset($this->info[$type])) {
823 trigger_error(
'Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
826 return $this->info[$type]->make($string);
834 public function set($type, $impl)
836 $this->info[$type] = $impl;
861 $definition =
$config->getHTMLDefinition();
862 $e =& $context->get(
'ErrorCollector',
true);
865 $ok =& $context->get(
'IDAccumulator',
true);
868 $context->register(
'IDAccumulator', $id_accumulator);
872 $current_token =& $context->get(
'CurrentToken',
true);
873 if (!$current_token) {
874 $context->register(
'CurrentToken', $token);
885 $d_defs = $definition->info_global_attr;
888 $attr = $token->attr;
892 foreach ($definition->info_attr_transform_pre
as $transform) {
893 $attr = $transform->transform(
$o = $attr,
$config, $context);
896 $e->send(E_NOTICE,
'AttrValidator: Attributes transformed',
$o, $attr);
903 foreach ($definition->info[$token->name]->attr_transform_pre
as $transform) {
904 $attr = $transform->transform(
$o = $attr,
$config, $context);
907 $e->send(E_NOTICE,
'AttrValidator: Attributes transformed',
$o, $attr);
915 $defs = $definition->info[$token->name]->attr;
918 $context->register(
'CurrentAttr', $attr_key);
922 foreach ($attr
as $attr_key => $value) {
925 if (isset($defs[$attr_key])) {
927 if ($defs[$attr_key] ===
false) {
936 $result = $defs[$attr_key]->validate(
942 } elseif (isset($d_defs[$attr_key])) {
945 $result = $d_defs[$attr_key]->validate(
956 if ($result ===
false || $result ===
null) {
960 $e->send(E_ERROR,
'AttrValidator: Attribute removed');
964 unset($attr[$attr_key]);
965 } elseif (is_string($result)) {
971 $attr[$attr_key] = $result;
983 $context->destroy(
'CurrentAttr');
988 foreach ($definition->info_attr_transform_post
as $transform) {
989 $attr = $transform->transform(
$o = $attr,
$config, $context);
992 $e->send(E_NOTICE,
'AttrValidator: Attributes transformed',
$o, $attr);
998 foreach ($definition->info[$token->name]->attr_transform_post
as $transform) {
999 $attr = $transform->transform(
$o = $attr,
$config, $context);
1002 $e->send(E_NOTICE,
'AttrValidator: Attributes transformed',
$o, $attr);
1007 $token->attr = $attr;
1010 if (!$current_token) {
1011 $context->destroy(
'CurrentToken');
1024 if (!defined(
'HTMLPURIFIER_PREFIX')) {
1025 define(
'HTMLPURIFIER_PREFIX', dirname(__FILE__) .
'/standalone');
1026 set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());
1031 if (!defined(
'PHP_EOL')) {
1032 switch (strtoupper(substr(PHP_OS, 0, 3))) {
1034 define(
'PHP_EOL',
"\r\n");
1037 define(
'PHP_EOL',
"\r");
1040 define(
'PHP_EOL',
"\n");
1070 require_once HTMLPURIFIER_PREFIX .
'/' . $file;
1081 if (strncmp(
'HTMLPurifier', $class, 12) !== 0) {
1085 if (strncmp(
'HTMLPurifier_Language_', $class, 22) === 0) {
1086 $code = str_replace(
'_',
'-', substr($class, 22));
1087 $file =
'HTMLPurifier/Language/classes/' . $code .
'.php';
1089 $file = str_replace(
'_',
'/', $class) .
'.php';
1091 if (!file_exists(HTMLPURIFIER_PREFIX .
'/' . $file)) {
1102 $autoload = array(
'HTMLPurifier_Bootstrap',
'autoload');
1103 if (($funcs = spl_autoload_functions()) ===
false) {
1104 spl_autoload_register($autoload);
1105 } elseif (function_exists(
'spl_autoload_unregister')) {
1106 if (version_compare(PHP_VERSION,
'5.3.0',
'>=')) {
1108 spl_autoload_register($autoload,
true,
true);
1110 $buggy = version_compare(PHP_VERSION,
'5.2.11',
'<');
1111 $compat = version_compare(PHP_VERSION,
'5.1.2',
'<=') &&
1112 version_compare(PHP_VERSION,
'5.1.0',
'>=');
1113 foreach ($funcs
as $func) {
1114 if ($buggy && is_array($func)) {
1117 $reflector =
new ReflectionMethod($func[0], $func[1]);
1118 if (!$reflector->isStatic()) {
1119 throw new Exception(
1120 'HTML Purifier autoloader registrar is not compatible
1121 with non-static object methods due to PHP Bug #44144;
1122 Please do not use HTMLPurifier.autoload.php (or any
1123 file that includes this file); instead, place the code:
1124 spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
1125 after your own autoloaders.'
1131 $func = implode(
'::', $func);
1134 spl_autoload_unregister($func);
1136 spl_autoload_register($autoload);
1137 foreach ($funcs
as $func) {
1138 spl_autoload_register($func);
1196 $this->
setup =
true;
1227 array(
'left',
'right',
'center',
'justify'),
1232 $this->info[
'border-bottom-style'] =
1233 $this->info[
'border-right-style'] =
1234 $this->info[
'border-left-style'] =
1254 array(
'none',
'left',
'right',
'both'),
1258 array(
'none',
'left',
'right'),
1262 array(
'normal',
'italic',
'oblique'),
1266 array(
'normal',
'small-caps'),
1278 array(
'inside',
'outside'),
1295 $this->info[
'list-style-image'] = $uri_or_none;
1300 array(
'capitalize',
'uppercase',
'lowercase',
'none'),
1305 $this->info[
'background-image'] = $uri_or_none;
1307 array(
'repeat',
'repeat-x',
'repeat-y',
'no-repeat')
1310 array(
'scroll',
'fixed')
1315 $this->info[
'border-top-color'] =
1316 $this->info[
'border-bottom-color'] =
1317 $this->info[
'border-left-color'] =
1318 $this->info[
'border-right-color'] =
1331 $this->info[
'border-top-width'] =
1332 $this->info[
'border-bottom-width'] =
1333 $this->info[
'border-left-width'] =
1387 $this->info[
'margin-top'] =
1388 $this->info[
'margin-bottom'] =
1389 $this->info[
'margin-left'] =
1402 $this->info[
'padding-top'] =
1403 $this->info[
'padding-bottom'] =
1404 $this->info[
'padding-left'] =
1442 $max =
$config->get(
'CSS.MaxImgLength');
1444 $this->info[
'width'] =
1445 $this->info[
'height'] =
1460 $this->info[
'min-width'] =
1461 $this->info[
'min-height'] =
1476 $this->info[
'max-width'] =
1477 $this->info[
'max-height'] =
1522 $this->info[
'border'] =
1523 $this->info[
'border-bottom'] =
1524 $this->info[
'border-top'] =
1525 $this->info[
'border-left'] =
1529 array(
'collapse',
'separate')
1533 array(
'top',
'bottom')
1537 array(
'auto',
'fixed')
1564 array(
'nowrap',
'normal',
'pre',
'pre-wrap',
'pre-line')
1567 if (
$config->get(
'CSS.Proprietary')) {
1571 if (
$config->get(
'CSS.AllowTricky')) {
1575 if (
$config->get(
'CSS.Trusted')) {
1579 $allow_important =
$config->get(
'CSS.AllowImportant');
1581 foreach ($this->info
as $k => $v) {
1609 $this->info[
'page-break-after'] =
1627 $this->info[
'border-top-left-radius'] =
1628 $this->info[
'border-top-right-radius'] =
1629 $this->info[
'border-bottom-right-radius'] =
1653 'table-header-group',
1654 'table-footer-group',
1656 'table-column-group',
1664 array(
'visible',
'hidden',
'collapse')
1676 array(
'static',
'relative',
'absolute',
'fixed')
1678 $this->info[
'top'] =
1679 $this->info[
'left'] =
1680 $this->info[
'right'] =
1706 $support =
"(for information on implementing this, see the " .
1708 $allowed_properties =
$config->get(
'CSS.AllowedProperties');
1709 if ($allowed_properties !==
null) {
1710 foreach ($this->info
as $name => $d) {
1711 if (!isset($allowed_properties[$name])) {
1712 unset($this->info[$name]);
1714 unset($allowed_properties[$name]);
1717 foreach ($allowed_properties
as $name => $d) {
1719 $name = htmlspecialchars($name);
1720 trigger_error(
"Style attribute '$name' is not supported $support", E_USER_WARNING);
1724 $forbidden_properties =
$config->get(
'CSS.ForbiddenProperties');
1725 if ($forbidden_properties !==
null) {
1726 foreach ($this->info
as $name => $d) {
1727 if (isset($forbidden_properties[$name])) {
1728 unset($this->info[$name]);
1897 $parent = $parent ? $parent : $definition->defaultPlist;
1899 $this->def = $definition;
1958 public function get($key, $a =
null)
1962 "Using deprecated API: use \$config->get('$key.$a') instead",
1967 if (!$this->finalized) {
1970 if (!isset($this->def->info[$key])) {
1973 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
1978 if (isset($this->def->info[$key]->isAlias)) {
1979 $d = $this->def->info[$key];
1981 'Cannot get value from aliased directive, use real name ' . $d->key,
1987 list($ns) = explode(
'.', $key);
1988 if ($ns !== $this->lock) {
1990 'Cannot get value of namespace ' . $ns .
' when lock for ' .
1992 ' is active, this probably indicates a Definition setup method ' .
1993 'is accessing directives that are not within its namespace',
1999 return $this->plist->get($key);
2011 if (!$this->finalized) {
2015 if (!isset($full[$namespace])) {
2017 'Cannot retrieve undefined namespace ' .
2018 htmlspecialchars($namespace),
2023 return $full[$namespace];
2038 if (
empty($this->serials[$namespace])) {
2039 $batch = $this->
getBatch($namespace);
2040 unset($batch[
'DefinitionRev']);
2041 $this->serials[$namespace] = sha1(
serialize($batch));
2043 return $this->serials[$namespace];
2054 if (
empty($this->serial)) {
2067 if (!$this->finalized) {
2071 foreach ($this->plist->squash()
as $name => $value) {
2072 list($ns, $key) = explode(
'.', $name, 2);
2073 $ret[$ns][$key] = $value;
2085 public function set($key, $value, $a =
null)
2087 if (strpos($key,
'.') ===
false) {
2089 $directive = $value;
2091 $key =
"$key.$directive";
2092 $this->
triggerError(
"Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
2094 list($namespace) = explode(
'.', $key);
2096 if ($this->
isFinalized(
'Cannot set directive after finalization')) {
2099 if (!isset($this->def->info[$key])) {
2101 'Cannot set undefined directive ' . htmlspecialchars($key) .
' to value',
2106 $def = $this->def->info[$key];
2108 if (isset(
$def->isAlias)) {
2109 if ($this->aliasMode) {
2111 'Double-aliases not allowed, please fix '.
2112 'ConfigSchema bug with' . $key,
2117 $this->aliasMode =
true;
2118 $this->
set(
$def->key, $value);
2119 $this->aliasMode =
false;
2120 $this->
triggerError(
"$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
2132 $allow_null = isset(
$def->allow_null);
2136 $value = $this->parser->parse($value, $type, $allow_null);
2139 'Value for ' . $key .
' is of invalid type, should be ' .
2145 if (is_string($value) && is_object(
$def)) {
2147 if (isset(
$def->aliases[$value])) {
2148 $value =
$def->aliases[$value];
2151 if (isset(
$def->allowed) && !isset(
$def->allowed[$value])) {
2153 'Value not supported, valid values are: ' .
2154 $this->_listify(
$def->allowed),
2160 $this->plist->set($key, $value);
2165 if ($namespace ==
'HTML' || $namespace ==
'CSS' || $namespace ==
'URI') {
2166 $this->definitions[$namespace] =
null;
2169 $this->serials[$namespace] =
false;
2179 private function _listify($lookup)
2182 foreach ($lookup
as $name => $b) {
2185 return implode(
', ', $list);
2264 if ($optimized && !$raw) {
2267 if (!$this->finalized) {
2271 $lock = $this->lock;
2274 $cache = $factory->create($type, $this);
2275 $this->lock = $lock;
2280 if (!
empty($this->definitions[$type])) {
2281 $def = $this->definitions[$type];
2287 if (
$def->optimized) {
2288 $cache->add(
$def, $this);
2294 $def = $cache->get($this);
2297 $this->definitions[$type] =
$def;
2301 $def = $this->initDefinition($type);
2303 $this->lock = $type;
2307 $cache->add(
$def, $this);
2316 if (is_null($this->
get($type .
'.DefinitionID'))) {
2319 "Cannot retrieve raw version without specifying %$type.DefinitionID"
2323 if (!
empty($this->definitions[$type])) {
2324 $def = $this->definitions[$type];
2325 if (
$def->setup && !$optimized) {
2326 $extra = $this->chatty ?
2327 " (try moving this code block earlier in your initialization)" :
2330 "Cannot retrieve raw definition after it has already been setup" .
2334 if (
$def->optimized ===
null) {
2335 $extra = $this->chatty ?
" (try flushing your cache)" :
"";
2337 "Optimization status of definition is unknown" . $extra
2340 if (
$def->optimized !== $optimized) {
2341 $msg = $optimized ?
"optimized" :
"unoptimized";
2342 $extra = $this->chatty ?
2343 " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
2346 "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
2368 $def = $cache->get($this);
2372 $this->definitions[$type] =
$def;
2378 if (!is_null($this->
get($type .
'.DefinitionID'))) {
2379 if ($this->chatty) {
2381 'Due to a documentation error in previous version of HTML Purifier, your ' .
2382 'definitions are not being cached. If this is OK, you can remove the ' .
2383 '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
2384 'modify your code to use maybeGetRawDefinition, and test if the returned ' .
2385 'value is null before making any edits (if it is null, that means that a ' .
2386 'cached version is available, and no raw operations are necessary). See ' .
2387 '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
2388 'Customize</a> for more details',
2393 "Useless DefinitionID declaration",
2400 $def = $this->initDefinition($type);
2401 $def->optimized = $optimized;
2415 private function initDefinition($type)
2418 if ($type ==
'HTML') {
2420 } elseif ($type ==
'CSS') {
2422 } elseif ($type ==
'URI') {
2426 "Definition of $type type not supported"
2429 $this->definitions[$type] =
$def;
2470 if ($this->
isFinalized(
'Cannot load directives after finalization')) {
2473 foreach ($config_array
as $key => $value) {
2474 $key = str_replace(
'_',
'.', $key);
2475 if (strpos($key,
'.') !==
false) {
2476 $this->
set($key, $value);
2479 $namespace_values = $value;
2480 foreach ($namespace_values
as $directive => $value2) {
2481 $this->
set($namespace .
'.'. $directive, $value2);
2502 if ($allowed !==
true) {
2503 if (is_string($allowed)) {
2504 $allowed = array($allowed);
2506 $allowed_ns = array();
2507 $allowed_directives = array();
2508 $blacklisted_directives = array();
2509 foreach ($allowed
as $ns_or_directive) {
2510 if (strpos($ns_or_directive,
'.') !==
false) {
2512 if ($ns_or_directive[0] ==
'-') {
2513 $blacklisted_directives[substr($ns_or_directive, 1)] =
true;
2515 $allowed_directives[$ns_or_directive] =
true;
2519 $allowed_ns[$ns_or_directive] =
true;
2524 foreach ($schema->info
as $key =>
$def) {
2525 list($ns, $directive) = explode(
'.', $key, 2);
2526 if ($allowed !==
true) {
2527 if (isset($blacklisted_directives[
"$ns.$directive"])) {
2530 if (!isset($allowed_directives[
"$ns.$directive"]) && !isset($allowed_ns[$ns])) {
2534 if (isset(
$def->isAlias)) {
2537 if ($directive ==
'DefinitionID' || $directive ==
'DefinitionRev') {
2540 $ret[] = array($ns, $directive);
2557 public static function loadArrayFromForm($array, $index =
false, $allowed =
true, $mq_fix =
true, $schema =
null)
2590 public static function prepareArrayFromForm($array, $index =
false, $allowed =
true, $mq_fix =
true, $schema =
null)
2592 if ($index !==
false) {
2593 $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
2595 $mq = $mq_fix && function_exists(
'get_magic_quotes_gpc') && get_magic_quotes_gpc();
2599 foreach ($allowed
as $key) {
2600 list($ns, $directive) = $key;
2601 $skey =
"$ns.$directive";
2602 if (!
empty($array[
"Null_$skey"])) {
2603 $ret[$ns][$directive] =
null;
2606 if (!isset($array[$skey])) {
2609 $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
2610 $ret[$ns][$directive] = $value;
2622 if ($this->
isFinalized(
'Cannot load directives after finalization')) {
2625 $array = parse_ini_file($filename,
true);
2638 if ($this->finalized && $error) {
2653 $this->plist->squash(
true);
2662 $this->finalized =
true;
2663 $this->parser =
null;
2677 if ($this->chatty) {
2678 $trace = debug_backtrace();
2680 for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
2682 if (isset($trace[$i + 1][
'class']) && $trace[$i + 1][
'class'] ===
'HTMLPurifier_Config') {
2685 $frame = $trace[$i];
2686 $extra =
" invoked on line {$frame['line']} in file {$frame['file']}";
2690 trigger_error($msg . $extra, $no);
2781 $contents = file_get_contents(HTMLPURIFIER_PREFIX .
'/HTMLPurifier/ConfigSchema/schema.ser');
2782 $r = unserialize($contents);
2784 $hash = sha1($contents);
2785 trigger_error(
"Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
2797 if ($prototype !==
null) {
2816 public function add($key, $default, $type, $allow_null)
2818 $obj =
new stdClass();
2821 $obj->allow_null =
true;
2823 $this->info[$key] = $obj;
2825 $this->defaultPlist->set($key, $default);
2838 if (!isset($this->info[$key]->aliases)) {
2839 $this->info[$key]->aliases = array();
2841 foreach ($aliases
as $alias => $real) {
2842 $this->info[$key]->aliases[$alias] = $real;
2855 $this->info[$key]->allowed = $allowed;
2865 $obj =
new stdClass;
2866 $obj->key = $new_key;
2867 $obj->isAlias =
true;
2868 $this->info[$key] = $obj;
2876 foreach ($this->info
as $key => $v) {
2877 if (count((array) $v) == 1) {
2878 $this->info[$key] = $v->type;
2879 } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
2880 $this->info[$key] = -$v->type;
2927 if (!is_array($modules)) {
2928 $modules = array($modules);
2932 foreach ($modules
as $module) {
2933 foreach ($module->content_sets
as $key => $value) {
2935 if (isset($this->lookup[$key])) {
2937 $this->lookup[$key] = array_merge($this->lookup[$key], $temp);
2939 $this->lookup[$key] = $temp;
2943 $old_lookup =
false;
2944 while ($old_lookup !== $this->lookup) {
2946 foreach ($this->lookup
as $i => $set) {
2948 foreach ($set
as $element => $x) {
2949 if (isset($this->lookup[$element])) {
2950 $add += $this->lookup[$element];
2951 unset($this->lookup[$i][$element]);
2954 $this->lookup[$i] += $add;
2958 foreach ($this->lookup
as $key =>
$lookup) {
2959 $this->info[$key] = implode(
' | ', array_keys(
$lookup));
2961 $this->keys = array_keys($this->info);
2962 $this->values = array_values($this->info);
2975 $content_model =
$def->content_model;
2976 if (is_string($content_model)) {
2978 $def->content_model = preg_replace_callback(
2979 '/\b(' . implode(
'|', $this->keys) .
')\b/',
2980 array($this,
'generateChildDefCallback'),
2991 return $this->info[$matches[0]];
3005 $value =
$def->content_model;
3006 if (is_object($value)) {
3008 'Literal object child definitions should be stored in '.
3009 'ElementDef->child not ElementDef->content_model',
3014 switch (
$def->content_model_type) {
3026 if ($module->defines_child_def) {
3027 $return = $module->getChildDef(
$def);
3029 if ($return !==
false) {
3034 'Could not determine which ChildDef class to instantiate',
3048 $array = explode(
'|', str_replace(
' ',
'', $string));
3050 foreach ($array
as $k) {
3075 private $_storage = array();
3082 public function register($name, &$ref)
3084 if (array_key_exists($name, $this->_storage)) {
3086 "Name $name produces collision, cannot re-register",
3091 $this->_storage[$name] =& $ref;
3100 public function &
get($name, $ignore_error =
false)
3102 if (!array_key_exists($name, $this->_storage)) {
3103 if (!$ignore_error) {
3105 "Attempted to retrieve non-existent variable $name",
3112 return $this->_storage[$name];
3121 if (!array_key_exists($name, $this->_storage)) {
3123 "Attempted to destroy non-existent variable $name",
3128 unset($this->_storage[$name]);
3138 return array_key_exists($name, $this->_storage);
3147 foreach ($context_array
as $key => $discard) {
3148 $this->
register($key, $context_array[$key]);
3188 return $config->version .
',' .
3202 if (substr_count($key,
',') < 2) {
3205 list($version,
$hash, $revision) = explode(
',', $key, 3);
3206 $compare = version_compare($version,
$config->version);
3208 if ($compare != 0) {
3213 $revision < $config->
get($this->type .
'.DefinitionRev')) {
3227 if (
$def->type !== $this->type) {
3228 trigger_error(
"Cannot use definition of type {$def->type} in cache for {$this->type}");
3295 protected $caches = array(
'Serializer' => array());
3323 if ($prototype !==
null) {
3324 $instance = $prototype;
3325 } elseif ($instance ===
null || $prototype ===
true) {
3337 public function register($short, $long)
3339 $this->implementations[$short] = $long;
3350 $method =
$config->get(
'Cache.DefinitionImpl');
3351 if ($method ===
null) {
3354 if (!
empty($this->caches[$method][$type])) {
3355 return $this->caches[$method][$type];
3357 if (isset($this->implementations[$method]) &&
3358 class_exists($class = $this->implementations[$method],
false)) {
3359 $cache =
new $class($type);
3361 if ($method !=
'Serializer') {
3362 trigger_error(
"Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
3366 foreach ($this->decorators
as $decorator) {
3367 $new_cache = $decorator->decorate($cache);
3370 $cache = $new_cache;
3372 $this->caches[$method][$type] = $cache;
3373 return $this->caches[$method][$type];
3382 if (is_string($decorator)) {
3383 $class =
"HTMLPurifier_DefinitionCache_Decorator_$decorator";
3384 $decorator =
new $class;
3386 $this->decorators[$decorator->name] = $decorator;
3459 $this->dtdPublic = $dtd_public;
3460 $this->dtdSystem = $dtd_system;
3496 public function register(
3500 $tidy_modules = array(),
3505 if (!is_array($modules)) {
3506 $modules = array($modules);
3508 if (!is_array($tidy_modules)) {
3509 $tidy_modules = array($tidy_modules);
3514 if (!is_object($doctype)) {
3525 $this->
doctypes[$doctype->name] = $doctype;
3526 $name = $doctype->name;
3528 foreach ($doctype->aliases
as $alias) {
3529 if (isset($this->
doctypes[$alias])) {
3532 $this->aliases[$alias] = $name;
3535 if (isset($this->aliases[$name])) {
3536 unset($this->aliases[$name]);
3548 public function get($doctype)
3550 if (isset($this->aliases[$doctype])) {
3551 $doctype = $this->aliases[$doctype];
3553 if (!isset($this->
doctypes[$doctype])) {
3554 trigger_error(
'Doctype ' . htmlspecialchars($doctype) .
' does not exist', E_USER_ERROR);
3584 $doctype =
$config->get(
'HTML.Doctype');
3585 if (!
empty($doctype)) {
3588 $doctype =
$config->get(
'HTML.CustomDoctype');
3589 if (!
empty($doctype)) {
3593 if (
$config->get(
'HTML.XHTML')) {
3594 $doctype =
'XHTML 1.0';
3596 $doctype =
'HTML 4.01';
3598 if (
$config->get(
'HTML.Strict')) {
3599 $doctype .=
' Strict';
3601 $doctype .=
' Transitional';
3764 foreach (
$def->attr
as $k => $v) {
3768 foreach ($v
as $v2) {
3769 $this->attr[0][] = $v2;
3774 if (isset($this->attr[$k])) {
3775 unset($this->attr[$k]);
3779 $this->attr[$k] = $v;
3781 $this->_mergeAssocArray($this->excludes,
$def->excludes);
3782 $this->attr_transform_pre = array_merge($this->attr_transform_pre,
$def->attr_transform_pre);
3783 $this->attr_transform_post = array_merge($this->attr_transform_post,
$def->attr_transform_post);
3786 $this->content_model =
3787 str_replace(
"#SUPER", $this->content_model,
$def->content_model);
3788 $this->child =
false;
3791 $this->content_model_type =
$def->content_model_type;
3792 $this->child =
false;
3794 if (!is_null(
$def->child)) {
3795 $this->child =
$def->child;
3797 if (!is_null(
$def->formatting)) {
3798 $this->formatting =
$def->formatting;
3800 if (
$def->descendants_are_inline) {
3801 $this->descendants_are_inline =
$def->descendants_are_inline;
3810 private function _mergeAssocArray(&$a1, $a2)
3812 foreach ($a2
as $k => $v) {
3814 if (isset($a1[$k])) {
3838 private function __construct()
3840 trigger_error(
'Cannot instantiate encoder, call methods statically', E_USER_ERROR);
3859 set_error_handler(array(
'HTMLPurifier_Encoder',
'muteErrorHandler'));
3860 $r =
iconv($in, $out, $text);
3861 restore_error_handler();
3873 public static function iconv($in, $out, $text, $max_chunk_size = 8000)
3876 if ($code == self::ICONV_OK) {
3878 } elseif ($code == self::ICONV_TRUNCATES) {
3881 if ($in ==
'utf-8') {
3882 if ($max_chunk_size < 4) {
3883 trigger_error(
'max_chunk_size is too small', E_USER_WARNING);
3888 if (($c = strlen($text)) <= $max_chunk_size) {
3894 if ($i + $max_chunk_size >= $c) {
3899 if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {
3900 $chunk_size = $max_chunk_size;
3901 } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {
3902 $chunk_size = $max_chunk_size - 1;
3903 } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {
3904 $chunk_size = $max_chunk_size - 2;
3905 } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {
3906 $chunk_size = $max_chunk_size - 3;
3910 $chunk = substr($text, $i, $chunk_size);
3965 '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
3985 $len = strlen($str);
3986 for ($i = 0; $i < $len; $i++) {
3987 $in = ord($str[$i]);
3992 if (0 == (0x80 & ($in))) {
3994 if (($in <= 31 || $in == 127) &&
3995 !($in == 9 || $in == 13 || $in == 10)
4004 } elseif (0xC0 == (0xE0 & ($in))) {
4007 $mUcs4 = ($mUcs4 & 0x1F) << 6;
4010 } elseif (0xE0 == (0xF0 & ($in))) {
4013 $mUcs4 = ($mUcs4 & 0x0F) << 12;
4016 } elseif (0xF0 == (0xF8 & ($in))) {
4019 $mUcs4 = ($mUcs4 & 0x07) << 18;
4022 } elseif (0xF8 == (0xFC & ($in))) {
4033 $mUcs4 = ($mUcs4 & 0x03) << 24;
4036 } elseif (0xFC == (0xFE & ($in))) {
4040 $mUcs4 = ($mUcs4 & 1) << 30;
4054 if (0x80 == (0xC0 & ($in))) {
4056 $shift = ($mState - 1) * 6;
4058 $tmp = ($tmp & 0x0000003F) << $shift;
4061 if (0 == --$mState) {
4068 if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
4069 ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
4070 ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
4073 (($mUcs4 & 0xFFFFF800) == 0xD800) ||
4078 } elseif (0xFEFF != $mUcs4 &&
4084 (0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
4087 (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
4088 (0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
4089 (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
4142 if ($code > 1114111
or $code < 0
or
4143 ($code >= 55296
and $code <= 57343) ) {
4149 $x = $y = $z = $w = 0;
4155 $x = ($code & 63) | 128;
4157 $y = (($code & 2047) >> 6) | 192;
4159 $y = (($code & 4032) >> 6) | 128;
4160 if ($code < 65536) {
4161 $z = (($code >> 12) & 15) | 224;
4163 $z = (($code >> 12) & 63) | 128;
4164 $w = (($code >> 18) & 7) | 240;
4189 static $iconv =
null;
4190 if ($iconv ===
null) {
4205 $encoding =
$config->get(
'Core.Encoding');
4206 if ($encoding ===
'utf-8') {
4209 static $iconv =
null;
4210 if ($iconv ===
null) {
4213 if ($iconv && !
$config->get(
'Test.ForceNoIconv')) {
4216 if ($str ===
false) {
4218 trigger_error(
'Invalid encoding ' . $encoding, E_USER_ERROR);
4224 $str = strtr($str, self::testEncodingSupportsASCII($encoding));
4226 } elseif ($encoding ===
'iso-8859-1') {
4227 $str = utf8_encode($str);
4231 if ($bug == self::ICONV_OK) {
4232 trigger_error(
'Encoding not supported, please install iconv', E_USER_ERROR);
4235 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .
4236 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',
4253 $encoding =
$config->get(
'Core.Encoding');
4254 if ($escape =
$config->get(
'Core.EscapeNonASCIICharacters')) {
4257 if ($encoding ===
'utf-8') {
4260 static $iconv =
null;
4261 if ($iconv ===
null) {
4264 if ($iconv && !
$config->get(
'Test.ForceNoIconv')) {
4267 if (!$escape && !
empty($ascii_fix)) {
4268 $clear_fix = array();
4269 foreach ($ascii_fix
as $utf8 => $native) {
4270 $clear_fix[$utf8] =
'';
4272 $str = strtr($str, $clear_fix);
4274 $str = strtr($str, array_flip($ascii_fix));
4276 $str =
self::iconv(
'utf-8', $encoding .
'//IGNORE', $str);
4278 } elseif ($encoding ===
'iso-8859-1') {
4279 $str = utf8_decode($str);
4282 trigger_error(
'Encoding not supported', E_USER_ERROR);
4310 $len = strlen($str);
4311 for ($i = 0; $i < $len; $i++) {
4312 $bytevalue = ord($str[$i]);
4313 if ($bytevalue <= 0x7F) {
4314 $result .= chr($bytevalue);
4316 } elseif ($bytevalue <= 0xBF) {
4317 $working = $working << 6;
4318 $working += ($bytevalue & 0x3F);
4320 if ($bytesleft <= 0) {
4321 $result .=
"&#" . $working .
";";
4323 } elseif ($bytevalue <= 0xDF) {
4324 $working = $bytevalue & 0x1F;
4326 } elseif ($bytevalue <= 0xEF) {
4327 $working = $bytevalue & 0x0F;
4330 $working = $bytevalue & 0x07;
4364 static $code =
null;
4365 if ($code ===
null) {
4367 $r =
self::unsafeIconv(
'utf-8',
'ascii//IGNORE',
"\xCE\xB1" . str_repeat(
'a', 9000));
4370 } elseif (($c = strlen($r)) < 9000) {
4372 } elseif ($c > 9000) {
4374 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .
4375 'include your iconv version as per phpversion()',
4403 static $encodings = array();
4405 if (isset($encodings[$encoding])) {
4406 return $encodings[$encoding];
4408 $lenc = strtolower($encoding);
4411 return array(
"\xC2\xA5" =>
'\\',
"\xE2\x80\xBE" =>
'~');
4413 return array(
"\xE2\x82\xA9" =>
'\\');
4415 if (strpos($lenc,
'iso-8859-') === 0) {
4420 if (self::unsafeIconv(
'UTF-8', $encoding,
'a') ===
false) {
4423 for ($i = 0x20; $i <= 0x7E; $i++) {
4429 ($r === $c && self::unsafeIconv($encoding,
'UTF-8//IGNORE', $r) !== $c)
4437 $encodings[$encoding] =
$ret;
4467 $file = HTMLPURIFIER_PREFIX .
'/HTMLPurifier/EntityLookup/entities.ser';
4469 $this->
table = unserialize(file_get_contents($file));
4480 static $instance =
null;
4482 $instance = $prototype;
4483 } elseif (!$instance) {
4531 $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";
4535 $this->_semiOptionalPrefixRegex =
"/&()()()($semi_optional)/";
4537 $this->_textEntitiesRegex =
4540 '[#]x([a-fA-F0-9]+);?|'.
4545 '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
4550 $this->_attrEntitiesRegex =
4553 '[#]x([a-fA-F0-9]+);?|'.
4558 '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
4562 "($semi_optional)(?![=;A-Za-z0-9])".
4576 return preg_replace_callback(
4577 $this->_textEntitiesRegex,
4578 array($this,
'entityCallback'),
4592 return preg_replace_callback(
4593 $this->_attrEntitiesRegex,
4594 array($this,
'entityCallback'),
4610 $entity = $matches[0];
4611 $hex_part = @$matches[1];
4612 $dec_part = @$matches[2];
4613 $named_part =
empty($matches[3]) ? (
empty($matches[4]) ?
"" : $matches[4]) : $matches[3];
4614 if ($hex_part !== NULL && $hex_part !==
"") {
4616 } elseif ($dec_part !== NULL && $dec_part !==
"") {
4619 if (!$this->_entity_lookup) {
4622 if (isset($this->_entity_lookup->table[$named_part])) {
4623 return $this->_entity_lookup->table[$named_part];
4629 if (!
empty($matches[3])) {
4630 return preg_replace_callback(
4631 $this->_semiOptionalPrefixRegex,
4632 array($this,
'entityCallback'),
4648 '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
4687 return preg_replace_callback(
4688 $this->_substituteEntitiesRegex,
4689 array($this,
'nonSpecialEntityCallback'),
4706 $entity = $matches[0];
4707 $is_num = (@$matches[0][1] ===
'#');
4709 $is_hex = (@$entity[2] ===
'x');
4710 $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
4712 if (isset($this->_special_dec2str[$code])) {
4717 if (isset($this->_special_ent2dec[$matches[3]])) {
4720 if (!$this->_entity_lookup) {
4723 if (isset($this->_entity_lookup->table[$matches[3]])) {
4724 return $this->_entity_lookup->table[$matches[3]];
4742 return preg_replace_callback(
4743 $this->_substituteEntitiesRegex,
4744 array($this,
'specialEntityCallback'),
4761 $entity = $matches[0];
4762 $is_num = (@$matches[0][1] ===
'#');
4764 $is_hex = (@$entity[2] ===
'x');
4765 $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
4766 return isset($this->_special_dec2str[$int]) ?
4767 $this->_special_dec2str[$int] :
4770 return isset($this->_special_ent2dec[$matches[3]]) ?
4771 $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
4837 $this->locale =&
$context->get(
'Locale');
4839 $this->_current =& $this->_stacks[0];
4840 $this->errors =& $this->_stacks[0];
4848 public function send($severity, $msg)
4851 if (func_num_args() > 2) {
4852 $args = func_get_args();
4857 $token = $this->
context->get(
'CurrentToken',
true);
4858 $line = $token ? $token->line : $this->
context->get(
'CurrentLine',
true);
4859 $col = $token ? $token->col : $this->
context->get(
'CurrentCol',
true);
4860 $attr = $this->
context->get(
'CurrentAttr',
true);
4864 if (!is_null($token)) {
4865 $args[
'CurrentToken'] = $token;
4867 if (!is_null($attr)) {
4868 $subst[
'$CurrentAttr.Name'] = $attr;
4869 if (isset($token->attr[$attr])) {
4870 $subst[
'$CurrentAttr.Value'] = $token->attr[$attr];
4875 $msg = $this->locale->getMessage($msg);
4877 $msg = $this->locale->formatMessage($msg, $args);
4880 if (!
empty($subst)) {
4881 $msg = strtr($msg, $subst);
4886 self::LINENO => $line,
4887 self::SEVERITY => $severity,
4888 self::MESSAGE => $msg,
4889 self::CHILDREN => array()
4891 $this->_current[] = $error;
4900 $new_struct->value = clone $token;
4902 if (is_int($line) && is_int($col)) {
4903 if (isset($this->lines[$line][$col])) {
4904 $struct = $this->lines[$line][$col];
4906 $struct = $this->lines[$line][$col] = $new_struct;
4909 ksort($this->lines[$line], SORT_NUMERIC);
4911 if (isset($this->lines[-1])) {
4912 $struct = $this->lines[-1];
4914 $struct = $this->lines[-1] = $new_struct;
4917 ksort($this->lines, SORT_NUMERIC);
4920 if (!
empty($attr)) {
4922 if (!$struct->value) {
4923 $struct->value = array($attr,
'PUT VALUE HERE');
4926 if (!
empty($cssprop)) {
4928 if (!$struct->value) {
4930 $struct->value = array($cssprop,
'PUT VALUE HERE');
4935 $struct->addError($severity, $msg);
4964 foreach ($this->lines
as $line => $col_array) {
4968 foreach ($col_array
as $col => $struct) {
4969 $this->_renderStruct(
$ret, $struct, $line, $col);
4972 if (isset($this->lines[-1])) {
4973 $this->_renderStruct(
$ret, $this->lines[-1]);
4977 return '<p>' . $this->locale->getMessage(
'ErrorCollector: No errors') .
'</p>';
4979 return '<ul><li>' . implode(
'</li><li>',
$ret) .
'</li></ul>';
4984 private function _renderStruct(&
$ret, $struct, $line =
null, $col =
null)
4986 $stack = array($struct);
4987 $context_stack = array(array());
4988 while ($current = array_pop($stack)) {
4989 $context = array_pop($context_stack);
4990 foreach ($current->errors
as $error) {
4991 list($severity, $msg) = $error;
4995 $error = $this->locale->getErrorName($severity);
4996 $string .=
"<span class=\"error e$severity\"><strong>$error</strong></span> ";
4997 if (!is_null($line) && !is_null($col)) {
4998 $string .=
"<em class=\"location\">Line $line, Column $col: </em> ";
5000 $string .=
'<em class="location">End of Document: </em> ';
5002 $string .=
'<strong class="description">' . $this->generator->escape($msg) .
'</strong> ';
5003 $string .=
'</div>';
5011 foreach ($current->children
as $array) {
5013 $stack = array_merge($stack, array_reverse($array,
true));
5014 for ($i = count($array); $i > 0; $i--) {
5080 if (!isset($this->children[
$type][$id])) {
5084 return $this->children[
$type][$id];
5093 $this->errors[] = array($severity, $message);
5185 private $_xhtml =
true;
5191 private $_scriptFix =
false;
5210 private $_flashCompat;
5216 private $_innerHTMLFix;
5223 private $_flashStack = array();
5238 $this->_scriptFix =
$config->get(
'Output.CommentScriptContents');
5239 $this->_innerHTMLFix =
$config->get(
'Output.FixInnerHTML');
5240 $this->_sortAttr =
$config->get(
'Output.SortAttr');
5241 $this->_flashCompat =
$config->get(
'Output.FlashCompat');
5242 $this->_def =
$config->getHTMLDefinition();
5243 $this->_xhtml = $this->_def->doctype->xml;
5259 for ($i = 0, $size = count($tokens); $i < $size; $i++) {
5260 if ($this->_scriptFix && $tokens[$i]->
name ===
'script'
5272 if (extension_loaded(
'tidy') && $this->config->get(
'Output.TidyFormat')) {
5278 'output-xhtml' => $this->_xhtml,
5279 'show-body-only' =>
true,
5280 'indent-spaces' => 2,
5285 $tidy->cleanRepair();
5286 $html = (string) $tidy;
5290 if ($this->config->get(
'Core.NormalizeNewlines')) {
5291 $nl = $this->config->get(
'Output.Newline');
5310 trigger_error(
'Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
5315 if ($this->_flashCompat) {
5316 if ($token->name ==
"object") {
5317 $flash =
new stdClass();
5318 $flash->attr = $token->attr;
5319 $flash->param = array();
5320 $this->_flashStack[] = $flash;
5323 return '<' . $token->name . ($attr ?
' ' :
'') . $attr .
'>';
5327 if ($this->_flashCompat) {
5328 if ($token->name ==
"object" && !
empty($this->_flashStack)) {
5332 return $_extra .
'</' . $token->name .
'>';
5335 if ($this->_flashCompat && $token->name ==
"param" && !
empty($this->_flashStack)) {
5336 $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr[
'name']] = $token->attr[
'value'];
5339 return '<' . $token->name . ($attr ?
' ' :
'') . $attr .
5340 ( $this->_xhtml ?
' /':
'' )
5344 return $this->
escape($token->data, ENT_NOQUOTES);
5347 return '<!--' . $token->data .
'-->';
5367 $data = preg_replace(
'#//\s*$#',
'', $token->data);
5368 return '<!--//--><![CDATA[//><!--' .
"\n" . trim($data) .
"\n" .
'//--><!]]>';
5382 if ($this->_sortAttr) {
5383 ksort($assoc_array_of_attributes);
5385 foreach ($assoc_array_of_attributes
as $key => $value) {
5386 if (!$this->_xhtml) {
5388 if (strpos($key,
':') !==
false) {
5392 if ($element && !
empty($this->_def->info[$element]->attr[$key]->minimized)) {
5393 $html .= $key .
' ';
5418 if ($this->_innerHTMLFix) {
5419 if (strpos($value,
'`') !==
false) {
5422 if (strcspn($value,
'"\' <>') === strlen($value)) {
5430 return rtrim(
$html);
5443 public function escape($string, $quote =
null)
5447 if ($quote ===
null) {
5448 $quote = ENT_COMPAT;
5450 return htmlspecialchars($string, $quote,
'UTF-8');
5571 if (!isset($module->info[$element_name])) {
5572 $element = $module->addBlankElement($element_name);
5574 $element = $module->info[$element_name];
5576 $element->attr[$attr_name] =
$def;
5584 public function addElement($element_name,
$type, $contents, $attr_collections, $attributes = array())
5589 $element = $module->addElement($element_name,
$type, $contents, $attr_collections, $attributes);
5604 $element = $module->addBlankElement($element_name);
5616 if (!$this->_anonModule) {
5618 $this->_anonModule->name =
'Anonymous';
5620 return $this->_anonModule;
5623 private $_anonModule =
null;
5652 unset($this->manager);
5655 foreach ($this->info
as $k => $v) {
5656 unset($this->info[$k]->content_model);
5657 unset($this->info[$k]->content_model_type);
5667 if ($this->_anonModule) {
5671 $this->manager->addModule($this->_anonModule);
5672 unset($this->_anonModule);
5675 $this->manager->setup(
$config);
5676 $this->doctype = $this->manager->doctype;
5678 foreach ($this->manager->modules
as $module) {
5679 foreach ($module->info_tag_transform
as $k => $v) {
5681 unset($this->info_tag_transform[$k]);
5683 $this->info_tag_transform[$k] = $v;
5686 foreach ($module->info_attr_transform_pre
as $k => $v) {
5688 unset($this->info_attr_transform_pre[$k]);
5690 $this->info_attr_transform_pre[$k] = $v;
5693 foreach ($module->info_attr_transform_post
as $k => $v) {
5695 unset($this->info_attr_transform_post[$k]);
5697 $this->info_attr_transform_post[$k] = $v;
5700 foreach ($module->info_injector
as $k => $v) {
5702 unset($this->info_injector[$k]);
5704 $this->info_injector[$k] = $v;
5708 $this->info = $this->manager->getElements();
5709 $this->info_content_sets = $this->manager->contentSets->lookup;
5718 $block_wrapper =
$config->get(
'HTML.BlockWrapper');
5719 if (isset($this->info_content_sets[
'Block'][$block_wrapper])) {
5720 $this->info_block_wrapper = $block_wrapper;
5723 'Cannot use non-block element as block wrapper',
5728 $parent =
$config->get(
'HTML.Parent');
5729 $def = $this->manager->getElement($parent,
true);
5731 $this->info_parent = $parent;
5732 $this->info_parent_def =
$def;
5735 'Cannot use unrecognized element as parent',
5738 $this->info_parent_def = $this->manager->getElement($this->info_parent,
true);
5742 $support =
"(for information on implementing this, see the support forums) ";
5746 $allowed_elements =
$config->get(
'HTML.AllowedElements');
5747 $allowed_attributes =
$config->get(
'HTML.AllowedAttributes');
5749 if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
5750 $allowed =
$config->get(
'HTML.Allowed');
5751 if (is_string($allowed)) {
5756 if (is_array($allowed_elements)) {
5757 foreach ($this->info
as $name => $d) {
5758 if (!isset($allowed_elements[$name])) {
5759 unset($this->info[$name]);
5761 unset($allowed_elements[$name]);
5764 foreach ($allowed_elements
as $element => $d) {
5765 $element = htmlspecialchars($element);
5766 trigger_error(
"Element '$element' is not supported $support", E_USER_WARNING);
5772 $allowed_attributes_mutable = $allowed_attributes;
5773 if (is_array($allowed_attributes)) {
5777 foreach ($this->info_global_attr
as $attr => $x) {
5778 $keys = array($attr,
"*@$attr",
"*.$attr");
5780 foreach ($keys
as $key) {
5781 if ($delete && isset($allowed_attributes[$key])) {
5784 if (isset($allowed_attributes_mutable[$key])) {
5785 unset($allowed_attributes_mutable[$key]);
5789 unset($this->info_global_attr[$attr]);
5793 foreach ($this->info
as $tag =>
$info) {
5794 foreach (
$info->attr
as $attr => $x) {
5795 $keys = array(
"$tag@$attr", $attr,
"*@$attr",
"$tag.$attr",
"*.$attr");
5797 foreach ($keys
as $key) {
5798 if ($delete && isset($allowed_attributes[$key])) {
5801 if (isset($allowed_attributes_mutable[$key])) {
5802 unset($allowed_attributes_mutable[$key]);
5806 if ($this->info[$tag]->attr[$attr]->required) {
5808 "Required attribute '$attr' in element '$tag' " .
5809 "was not allowed, which means '$tag' will not be allowed either",
5813 unset($this->info[$tag]->attr[$attr]);
5818 foreach ($allowed_attributes_mutable
as $elattr => $d) {
5819 $bits = preg_split(
'/[.@]/', $elattr, 2);
5823 if ($bits[0] !==
'*') {
5824 $element = htmlspecialchars($bits[0]);
5825 $attribute = htmlspecialchars($bits[1]);
5826 if (!isset($this->info[$element])) {
5828 "Cannot allow attribute '$attribute' if element " .
5829 "'$element' is not allowed/supported $support"
5833 "Attribute '$attribute' in element '$element' not supported $support",
5841 $attribute = htmlspecialchars($bits[0]);
5843 "Global attribute '$attribute' is not ".
5844 "supported in any elements $support",
5854 $forbidden_elements =
$config->get(
'HTML.ForbiddenElements');
5855 $forbidden_attributes =
$config->get(
'HTML.ForbiddenAttributes');
5857 foreach ($this->info
as $tag =>
$info) {
5858 if (isset($forbidden_elements[$tag])) {
5859 unset($this->info[$tag]);
5862 foreach (
$info->attr
as $attr => $x) {
5863 if (isset($forbidden_attributes[
"$tag@$attr"]) ||
5864 isset($forbidden_attributes[
"*@$attr"]) ||
5865 isset($forbidden_attributes[$attr])
5867 unset($this->info[$tag]->attr[$attr]);
5869 } elseif (isset($forbidden_attributes[
"$tag.$attr"])) {
5872 "Error with $tag.$attr: tag.attr syntax not supported for " .
5873 "HTML.ForbiddenAttributes; use tag@attr instead",
5879 foreach ($forbidden_attributes
as $key => $v) {
5880 if (strlen($key) < 2) {
5883 if ($key[0] !=
'*') {
5886 if ($key[1] ==
'.') {
5888 "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
5895 foreach ($this->info_injector
as $i => $injector) {
5896 if ($injector->checkNeeded(
$config) !==
false) {
5899 unset($this->info_injector[$i]);
5915 $list = str_replace(array(
' ',
"\t"),
'', $list);
5917 $elements = array();
5918 $attributes = array();
5920 $chunks = preg_split(
'/(,|[\n\r]+)/', $list);
5921 foreach ($chunks
as $chunk) {
5922 if (
empty($chunk)) {
5926 if (!strpos($chunk,
'[')) {
5930 list($element, $attr) = explode(
'[', $chunk);
5932 if ($element !==
'*') {
5933 $elements[$element] =
true;
5938 $attr = substr($attr, 0, strlen($attr) - 1);
5939 $attr = explode(
'|', $attr);
5940 foreach ($attr
as $key) {
5941 $attributes[
"$element.$key"] =
true;
5944 return array($elements, $attributes);
6093 public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array())
6097 list($content_model_type, $content_model) = $this->
parseContents($contents);
6107 $content_model_type,
6111 if (!is_string($contents)) {
6112 $this->info[$element]->child = $contents;
6114 return $this->info[$element];
6125 if (!isset($this->info[$element])) {
6128 $this->info[$element]->standalone =
false;
6130 trigger_error(
"Definition for $element already exists in module, cannot redefine");
6132 return $this->info[$element];
6143 if (!isset($this->content_sets[$type])) {
6144 $this->content_sets[$type] =
'';
6146 $this->content_sets[$type] .=
' | ';
6148 $this->content_sets[$type] .= $element;
6163 if (!is_string($contents)) {
6164 return array(
null,
null);
6166 switch ($contents) {
6169 return array(
'empty',
'');
6171 return array(
'optional',
'Inline | #PCDATA');
6173 return array(
'optional',
'Flow | #PCDATA');
6175 list($content_model_type, $content_model) = explode(
':', $contents);
6176 $content_model_type = strtolower(trim($content_model_type));
6177 $content_model = trim($content_model);
6178 return array($content_model_type, $content_model);
6189 if (!is_array($attr_includes)) {
6190 if (
empty($attr_includes)) {
6191 $attr_includes = array();
6193 $attr_includes = array($attr_includes);
6196 $attr[0] = $attr_includes;
6209 if (is_string($list)) {
6210 $list = func_get_args();
6213 foreach ($list
as $value) {
6214 if (is_null($value)) {
6217 $ret[$value] =
true;
6316 'CommonAttributes',
'Text',
'Hypertext',
'List',
6317 'Presentation',
'Edit',
'Bdo',
'Tables',
'Image',
6320 'Scripting',
'Object',
'Forms',
6324 $transitional = array(
'Legacy',
'Target',
'Iframe');
6325 $xml = array(
'XMLCommonAttributes');
6326 $non_xml = array(
'NonXMLCommonAttributes');
6330 'HTML 4.01 Transitional',
6332 array_merge($common, $transitional, $non_xml),
6333 array(
'Tidy_Transitional',
'Tidy_Proprietary'),
6335 '-//W3C//DTD HTML 4.01 Transitional//EN',
6336 'http://www.w3.org/TR/html4/loose.dtd'
6342 array_merge($common, $non_xml),
6343 array(
'Tidy_Strict',
'Tidy_Proprietary',
'Tidy_Name'),
6345 '-//W3C//DTD HTML 4.01//EN',
6346 'http://www.w3.org/TR/html4/strict.dtd'
6350 'XHTML 1.0 Transitional',
6352 array_merge($common, $transitional, $xml, $non_xml),
6353 array(
'Tidy_Transitional',
'Tidy_XHTML',
'Tidy_Proprietary',
'Tidy_Name'),
6355 '-//W3C//DTD XHTML 1.0 Transitional//EN',
6356 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
6362 array_merge($common, $xml, $non_xml),
6363 array(
'Tidy_Strict',
'Tidy_XHTML',
'Tidy_Strict',
'Tidy_Proprietary',
'Tidy_Name'),
6365 '-//W3C//DTD XHTML 1.0 Strict//EN',
6366 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
6374 array_merge($common, $xml, array(
'Ruby',
'Iframe')),
6375 array(
'Tidy_Strict',
'Tidy_XHTML',
'Tidy_Proprietary',
'Tidy_Strict',
'Tidy_Name'),
6377 '-//W3C//DTD XHTML 1.1//EN',
6378 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
6406 if (is_string($module)) {
6408 $original_module = $module;
6410 foreach ($this->prefixes
as $prefix) {
6411 $module = $prefix . $original_module;
6412 if (class_exists($module)) {
6418 $module = $original_module;
6419 if (!class_exists($module)) {
6421 $original_module .
' module does not exist',
6427 $module =
new $module();
6429 if (
empty($module->name)) {
6430 trigger_error(
'Module instance of ' . get_class($module) .
' must have name');
6433 if (!$overload && isset($this->registeredModules[$module->name])) {
6434 trigger_error(
'Overloading ' . $module->name .
' without explicit overload parameter', E_USER_WARNING);
6436 $this->registeredModules[$module->name] = $module;
6446 if (is_object($module)) {
6447 $module = $module->name;
6449 $this->userModules[] = $module;
6458 $this->prefixes[] = $prefix;
6468 $this->trusted =
$config->get(
'HTML.Trusted');
6472 $modules = $this->doctype->modules;
6475 $lookup =
$config->get(
'HTML.AllowedModules');
6476 $special_cases =
$config->get(
'HTML.CoreModules');
6478 if (is_array($lookup)) {
6480 if (isset($special_cases[$m])) {
6483 if (!isset($lookup[$m])) {
6490 if (
$config->get(
'HTML.Proprietary')) {
6493 if (
$config->get(
'HTML.SafeObject')) {
6496 if (
$config->get(
'HTML.SafeEmbed')) {
6499 if (
$config->get(
'HTML.SafeScripting') !== array()) {
6502 if (
$config->get(
'HTML.Nofollow')) {
6505 if (
$config->get(
'HTML.TargetBlank')) {
6510 if (
$config->get(
'HTML.TargetNoreferrer')) {
6513 if (
$config->get(
'HTML.TargetNoopener')) {
6525 foreach ($this->doctype->tidyModules
as $module) {
6533 foreach ($module->info_injector
as $injector) {
6534 if (!is_object($injector)) {
6535 $class =
"HTMLPurifier_Injector_$injector";
6536 $injector =
new $class;
6538 $n[$injector->name] = $injector;
6540 $module->info_injector = $n;
6545 foreach ($module->info
as $name =>
$def) {
6546 if (!isset($this->elementLookup[$name])) {
6547 $this->elementLookup[$name] = array();
6549 $this->elementLookup[$name][] = $module->name;
6574 if (!isset($this->registeredModules[$module]) || is_object($module)) {
6577 $this->
modules[$module] = $this->registeredModules[$module];
6586 $elements = array();
6588 if (!$this->trusted && !$module->safe) {
6591 foreach ($module->info
as $name => $v) {
6592 if (isset($elements[$name])) {
6601 foreach ($elements
as $n => $v) {
6603 unset($elements[$n]);
6623 if (!isset($this->elementLookup[$name])) {
6635 foreach ($this->elementLookup[$name]
as $module_name) {
6636 $module = $this->
modules[$module_name];
6647 $new_def = clone $module->info[$name];
6649 if (!
$def && $new_def->standalone) {
6654 $def->mergeIn($new_def);
6670 $this->attrCollections->performInclusions(
$def->attr);
6671 $this->attrCollections->expandIdentifiers(
$def->attr, $this->attrTypes);
6674 if (is_string(
$def->content_model) &&
6675 strpos(
$def->content_model,
'Inline') !==
false) {
6676 if ($name !=
'del' && $name !=
'ins') {
6678 $def->descendants_are_inline =
true;
6682 $this->contentSets->generateChildDef(
$def, $module);
6692 foreach (
$def->attr
as $attr_name => $attr_def) {
6693 if ($attr_def->required) {
6694 $def->required_attr[] = $attr_name;
6729 $id_accumulator->load(
$config->get(
'Attr.IDBlacklist'));
6730 return $id_accumulator;
6740 if (isset($this->ids[$id])) {
6743 return $this->ids[$id] =
true;
6751 public function load($array_of_ids)
6753 foreach ($array_of_ids
as $id) {
6754 $this->ids[$id] =
true;
6859 $this->htmlDefinition =
$config->getHTMLDefinition();
6864 if ($result !==
false) {
6867 $this->currentNesting =& $context->get(
'CurrentNesting');
6868 $this->currentToken =& $context->get(
'CurrentToken');
6869 $this->inputZipper =& $context->get(
'InputZipper');
6883 foreach ($this->needed
as $element => $attributes) {
6884 if (is_int($element)) {
6885 $element = $attributes;
6887 if (!isset(
$def->info[$element])) {
6890 if (!is_array($attributes)) {
6893 foreach ($attributes
as $name) {
6894 if (!isset(
$def->info[$element]->attr[
$name])) {
6895 return "$element.$name";
6909 if (!
empty($this->currentNesting)) {
6910 $parent_token = array_pop($this->currentNesting);
6911 $this->currentNesting[] = $parent_token;
6912 $parent = $this->htmlDefinition->info[$parent_token->name];
6914 $parent = $this->htmlDefinition->info_parent_def;
6916 if (!isset($parent->child->elements[
$name]) || isset($parent->excludes[
$name])) {
6920 if (!
empty($this->currentNesting)) {
6921 for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
6922 $node = $this->currentNesting[$i];
6923 $def = $this->htmlDefinition->info[$node->name];
6945 $i = count($this->inputZipper->back) - 1;
6952 $current = $this->inputZipper->back[$i];
6968 $result = $this->
forward($i, $current);
6972 if ($nesting ===
null) {
6978 if ($nesting <= 0) {
6999 $i = count($this->inputZipper->front) - 1;
7006 $current = $this->inputZipper->front[$i];
7119 if ($this->_loaded) {
7123 $factory->loadLanguage($this->
code);
7124 foreach ($factory->keys
as $key) {
7127 $this->_loaded =
true;
7137 if (!$this->_loaded) {
7140 if (!isset($this->messages[$key])) {
7143 return $this->messages[$key];
7153 if (!$this->_loaded) {
7156 if (!isset($this->errorNames[$int])) {
7157 return "[Error: $int]";
7159 return $this->errorNames[$int];
7170 $sep_last = $this->
getMessage(
'Item separator last');
7172 for ($i = 0, $c = count($array); $i < $c; $i++) {
7174 } elseif ($i + 1 < $c) {
7194 if (!$this->_loaded) {
7197 if (!isset($this->messages[$key])) {
7200 $raw = $this->messages[$key];
7203 foreach ($args
as $i => $value) {
7204 if (is_object($value)) {
7208 $generator = $this->
context->get(
'Generator');
7210 if (isset($value->name)) {
7211 $subst[
'$'.$i.
'.Name'] = $value->name;
7213 if (isset($value->data)) {
7214 $subst[
'$'.$i.
'.Data'] = $value->data;
7216 $subst[
'$'.$i.
'.Compact'] =
7217 $subst[
'$'.$i.
'.Serialized'] = $generator->generateFromToken($value);
7221 if (!
empty($value->attr)) {
7222 $stripped_token = clone $value;
7223 $stripped_token->attr = array();
7224 $subst[
'$'.$i.
'.Compact'] = $generator->generateFromToken($stripped_token);
7226 $subst[
'$'.$i.
'.Line'] = $value->line ? $value->line :
'unknown';
7229 } elseif (is_array($value)) {
7230 $keys = array_keys($value);
7231 if (array_keys($keys) === $keys) {
7233 $subst[
'$'.$i] = $this->
listify($value);
7237 $subst[
'$'.$i.
'.Keys'] = $this->
listify($keys);
7238 $subst[
'$'.$i.
'.Values'] = $this->
listify(array_values($value));
7242 $subst[
'$' . $i] = $value;
7244 return strtr($raw, $subst);
7274 public $keys = array(
'fallback',
'messages',
'errorNames');
7310 static $instance =
null;
7311 if ($prototype !==
null) {
7312 $instance = $prototype;
7313 } elseif ($instance ===
null || $prototype ==
true) {
7327 $this->dir = HTMLPURIFIER_PREFIX .
'/HTMLPurifier';
7340 if ($code ===
false) {
7341 $code = $this->validator->validate(
7342 $config->get(
'Core.Language'),
7347 $code = $this->validator->validate($code,
$config, $context);
7349 if ($code ===
false) {
7353 $pcode = str_replace(
'-',
'_', $code);
7356 if ($code ==
'en') {
7359 $class =
'HTMLPurifier_Language_' . $pcode;
7360 $file = $this->dir .
'/Language/classes/' . $code .
'.php';
7361 if (file_exists($file) || class_exists($class,
false)) {
7362 $lang =
new $class(
$config, $context);
7366 $fallback = $raw_fallback ? $raw_fallback :
'en';
7369 if (!$raw_fallback) {
7370 $lang->error =
true;
7375 $lang->code = $code;
7388 return $this->cache[$code][
'fallback'];
7397 static $languages_seen = array();
7400 if (isset($this->cache[$code])) {
7405 $filename = $this->dir .
'/Language/messages/' . $code .
'.php';
7408 $fallback = ($code !=
'en') ?
'en' :
false;
7411 if (!file_exists($filename)) {
7413 $filename = $this->dir .
'/Language/messages/en.php';
7417 $cache = compact($this->keys);
7424 if (isset($languages_seen[$code])) {
7426 'Circular fallback reference in language ' .
7432 $language_seen[$code] =
true;
7436 $fallback_cache = $this->cache[
$fallback];
7439 foreach ($this->keys
as $key) {
7440 if (isset(
$cache[$key]) && isset($fallback_cache[$key])) {
7441 if (isset($this->mergeable_keys_map[$key])) {
7443 } elseif (isset($this->mergeable_keys_list[$key])) {
7444 $cache[$key] = array_merge($fallback_cache[$key],
$cache[$key]);
7447 $cache[$key] = $fallback_cache[$key];
7453 $this->cache[$code] =
$cache;
7492 'em' =>
true,
'ex' =>
true,
'px' =>
true,
'in' =>
true,
7493 'cm' =>
true,
'mm' =>
true,
'pt' =>
true,
'pc' =>
true,
7494 'ch' =>
true,
'rem' =>
true,
'vw' =>
true,
'vh' =>
true,
7495 'vmin' =>
true,
'vmax' =>
true
7504 $this->n = (string)
$n;
7505 $this->unit = $u !==
false ? (string) $u :
false;
7518 $n_length = strspn(
$s,
'1234567890.+-');
7519 $n = substr(
$s, 0, $n_length);
7520 $unit = substr(
$s, $n_length);
7534 if ($this->n ===
'+0' || $this->n ===
'-0') {
7537 if ($this->n ===
'0' && $this->unit ===
false) {
7540 if (!ctype_lower($this->unit)) {
7541 $this->unit = strtolower($this->unit);
7548 $result =
$def->validate($this->n,
false,
false);
7549 if ($result ===
false) {
7592 if ($this->
isValid ===
null) {
7610 if ($l->unit !== $this->unit) {
7612 $l = $converter->convert($l, $this->unit);
7617 return $this->n - $l->n;
7696 "Passing a prototype to
7697 HTMLPurifier_Lexer::create() is deprecated, please instead
7698 use %Core.LexerImpl",
7702 $lexer =
$config->get(
'Core.LexerImpl');
7706 $config->get(
'Core.MaintainLineNumbers') ||
7707 $config->get(
'Core.CollectErrors');
7710 if (is_object($lexer)) {
7713 if (is_null($lexer)) {
7716 if ($needs_tracking) {
7717 $lexer =
'DirectLex';
7721 if (class_exists(
'DOMDocument',
false) &&
7722 method_exists(
'DOMDocument',
'loadHTML') &&
7723 !extension_loaded(
'domxml')
7731 $lexer =
'DirectLex';
7749 "Cannot instantiate unrecognized Lexer type " .
7750 htmlspecialchars($lexer)
7761 if ($needs_tracking && !$inst->tracksLineNumbers) {
7763 'Cannot use lexer that does not support line numbers with ' .
7764 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'
7814 if ($string ===
'') {
7819 $num_amp = substr_count($string,
'&') - substr_count($string,
'& ') -
7820 ($string[strlen($string) - 1] ===
'&' ? 1 : 0);
7825 $num_esc_amp = substr_count($string,
'&');
7826 $string = strtr($string, $this->_special_entity2str);
7829 $num_amp_2 = substr_count($string,
'&') - substr_count($string,
'& ') -
7830 ($string[strlen($string) - 1] ===
'&' ? 1 : 0);
7832 if ($num_amp_2 <= $num_esc_amp) {
7837 if (
$config->get(
'Core.LegacyEntityDecoder')) {
7838 $string = $this->_entity_parser->substituteSpecialEntities($string);
7841 $string = $this->_entity_parser->substituteAttrEntities($string);
7843 $string = $this->_entity_parser->substituteTextEntities($string);
7858 trigger_error(
'Call to abstract class', E_USER_ERROR);
7868 return preg_replace_callback(
7869 '/<!\[CDATA\[(.+?)\]\]>/s',
7870 array(
'HTMLPurifier_Lexer',
'CDATACallback'),
7882 return preg_replace_callback(
7883 '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
7884 array(
'HTMLPurifier_Lexer',
'CDATACallback'),
7896 return preg_replace(
7897 '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si',
7915 return htmlspecialchars($matches[1], ENT_COMPAT,
'UTF-8');
7930 if (
$config->get(
'Core.NormalizeNewlines')) {
7935 if (
$config->get(
'HTML.Trusted')) {
7946 if (
$config->get(
'Core.ConvertDocumentToFragment')) {
7948 if (
$config->get(
'Core.CollectErrors')) {
7949 $e =& $context->get(
'ErrorCollector');
7952 if ($e && $new_html !=
$html) {
7953 $e->send(E_WARNING,
'Lexer: Extracted body');
7959 if (
$config->get(
'Core.LegacyEntityDecoder')) {
7960 $html = $this->_entity_parser->substituteNonSpecialEntities(
$html);
7969 if (
$config->get(
'Core.RemoveProcessingInstructions')) {
7970 $html = preg_replace(
'#<\?.+?\?>#s',
'',
$html);
7973 $hidden_elements =
$config->get(
'Core.HiddenElements');
7974 if (
$config->get(
'Core.AggressivelyRemoveScript') &&
7975 !(
$config->get(
'HTML.Trusted') || !
$config->get(
'Core.RemoveScriptContents')
7976 ||
empty($hidden_elements[
"script"]))) {
7977 $html = preg_replace(
'#<script[^>]*>.*?</script>#i',
'',
$html);
7990 $result = preg_match(
'|(.*?)<body[^>]*>(.*)</body>|is',
$html, $matches);
7993 $comment_start = strrpos($matches[1],
'<!--');
7994 $comment_end = strrpos($matches[1],
'-->');
7995 if ($comment_start ===
false ||
7996 ($comment_end !==
false && $comment_end > $comment_start)) {
8082 for ($i = 48; $i <= 57; $i++) {
8083 $this->preserve[$i] =
true;
8085 for ($i = 65; $i <= 90; $i++) {
8086 $this->preserve[$i] =
true;
8088 for ($i = 97; $i <= 122; $i++) {
8089 $this->preserve[$i] =
true;
8091 $this->preserve[45] =
true;
8092 $this->preserve[46] =
true;
8093 $this->preserve[95] =
true;
8094 $this->preserve[126]=
true;
8098 for ($i = 0, $c = strlen(
$preserve); $i < $c; $i++) {
8099 $this->preserve[ord(
$preserve[$i])] =
true;
8117 for ($i = 0, $c = strlen($string); $i < $c; $i++) {
8118 if ($string[$i] !==
'%' && !isset($this->preserve[$int = ord($string[$i])])) {
8119 $ret .=
'%' . sprintf(
'%02X', $int);
8121 $ret .= $string[$i];
8137 if ($string ==
'') {
8140 $parts = explode(
'%', $string);
8141 $ret = array_shift($parts);
8142 foreach ($parts
as $part) {
8143 $length = strlen($part);
8145 $ret .=
'%25' . $part;
8148 $encoding = substr($part, 0, 2);
8149 $text = substr($part, 2);
8150 if (!ctype_xdigit($encoding)) {
8151 $ret .=
'%25' . $part;
8154 $int = hexdec($encoding);
8155 if (isset($this->preserve[$int])) {
8156 $ret .= chr($int) . $text;
8159 $encoding = strtoupper($encoding);
8160 $ret .=
'%' . $encoding . $text;
8206 public function get($name)
8208 if ($this->
has($name)) {
8209 return $this->data[$name];
8212 if ($this->parent) {
8213 return $this->parent->get($name);
8223 public function set($name, $value)
8225 $this->data[$name] = $value;
8235 return array_key_exists($name, $this->data);
8245 if ($name ==
null) {
8246 $this->data = array();
8248 unset($this->data[$name]);
8260 if ($this->cache !==
null && !$force) {
8263 if ($this->parent) {
8264 return $this->cache = array_merge($this->parent->squash($force), $this->data);
8285 $this->parent = $plist;
8314 parent::__construct($iterator);
8324 $key = $this->getInnerIterator()->key();
8325 if (strncmp($key, $this->filter, $this->l) !== 0) {
8358 $this->input = $input;
8359 $this->output = array();
8366 if (
empty($this->output)) {
8367 $this->output = array_reverse($this->input);
8368 $this->input = array();
8370 if (
empty($this->output)) {
8373 return array_pop($this->output);
8380 array_push($this->input, $x);
8387 return empty($this->input) &&
empty($this->output);
8442 $this->accessed[$index] =
true;
8443 return parent::offsetGet($index);
8460 $this->accessed = array();
8508 if (!file_exists($file)) {
8511 $fh = fopen($file,
'r');
8527 if (!file_exists($file)) {
8531 $fh = fopen($file,
'r');
8535 while (!feof($fh)) {
8558 if ($line ===
false) {
8561 $line = rtrim($line,
"\n\r");
8562 if (!$state && $line ===
'') {
8565 if ($line ===
'----') {
8568 if (strncmp(
'--#', $line, 3) === 0) {
8571 } elseif (strncmp(
'--', $line, 2) === 0) {
8573 $state = trim($line,
'- ');
8574 if (!isset(
$ret[$state])) {
8578 } elseif (!$state) {
8580 if (strpos($line,
':') !==
false) {
8582 list($state, $line) = explode(
':', $line, 2);
8583 $line = trim($line);
8590 $ret[$state] = $line;
8594 $ret[$state] .=
"$line\n";
8596 }
while (!feof($fh));
8634 $attr[
'style'] = isset($attr[
'style']) ? $attr[
'style'] :
'';
8635 $attr[
'style'] = $css . $attr[
'style'];
8690 if ($n ===
'type') {
8691 trigger_error(
'Deprecated type property called; use instanceof', E_USER_NOTICE);
8692 switch (get_class($this)) {
8693 case 'HTMLPurifier_Token_Start':
8695 case 'HTMLPurifier_Token_Empty':
8697 case 'HTMLPurifier_Token_End':
8699 case 'HTMLPurifier_Token_Text':
8701 case 'HTMLPurifier_Token_Comment':
8804 $p = clone $this->p_start;
8805 $p->__construct($name, $attr);
8816 $p = clone $this->p_end;
8817 $p->__construct($name);
8829 $p = clone $this->p_empty;
8830 $p->__construct($name, $attr);
8841 $p = clone $this->p_text;
8842 $p->__construct($data);
8853 $p = clone $this->p_comment;
8854 $p->__construct($data);
8922 $this->host =
$host;
8924 $this->path =
$path;
8938 if ($this->scheme !==
null) {
8939 $scheme_obj = $registry->getScheme($this->scheme,
$config, $context);
8946 $scheme_obj =
$def->getDefaultScheme(
$config, $context);
8948 if (
$def->defaultScheme !==
null) {
8951 'Default scheme object "' .
$def->defaultScheme .
'" was not readable',
8971 $chars_sub_delims =
'!$&\'()*+,;=';
8972 $chars_gen_delims =
':/?#[]@';
8973 $chars_pchar = $chars_sub_delims .
':@';
8976 if (!is_null($this->host)) {
8978 $this->host = $host_def->validate($this->host,
$config, $context);
8979 if ($this->host ===
false) {
8990 if (!is_null($this->scheme) && is_null($this->host) || $this->host ===
'') {
8994 if (
$def->defaultScheme === $this->scheme) {
8995 $this->scheme =
null;
9000 if (!is_null($this->userinfo)) {
9002 $this->userinfo = $encoder->encode($this->userinfo);
9006 if (!is_null($this->port)) {
9007 if ($this->port < 1 || $this->port > 65535) {
9014 if (!is_null($this->host)) {
9023 $this->path = $segments_encoder->encode($this->path);
9024 } elseif ($this->path !==
'') {
9025 if ($this->path[0] ===
'/') {
9029 if (strlen($this->path) >= 2 && $this->path[1] ===
'/') {
9036 $this->path = $segments_encoder->encode($this->path);
9038 } elseif (!is_null($this->scheme)) {
9042 $this->path = $segments_encoder->encode($this->path);
9048 $c = strpos($this->path,
'/');
9051 $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
9052 $segments_encoder->encode(substr($this->path, $c));
9054 $this->path = $segment_nc_encoder->encode($this->path);
9065 if (!is_null($this->query)) {
9066 $this->query = $qf_encoder->encode($this->query);
9069 if (!is_null($this->fragment)) {
9070 $this->fragment = $qf_encoder->encode($this->fragment);
9086 if (!is_null($this->host)) {
9088 if (!is_null($this->userinfo)) {
9089 $authority .= $this->userinfo .
'@';
9092 if (!is_null($this->port)) {
9104 if (!is_null($this->scheme)) {
9105 $result .= $this->scheme .
':';
9107 if (!is_null($authority)) {
9108 $result .=
'//' . $authority;
9111 if (!is_null($this->query)) {
9114 if (!is_null($this->fragment)) {
9135 if ($this->host ===
null) {
9138 $uri_def =
$config->getDefinition(
'URI');
9139 if ($uri_def->host === $this->host) {
9166 $current_scheme_obj =
$config->getDefinition(
'URI')->getDefaultScheme(
$config, $context);
9167 if ($current_scheme_obj->secure) {
9168 if (!$scheme_obj->secure) {
9216 $this->registeredFilters[$filter->name] = $filter;
9221 $r = $filter->prepare(
$config);
9222 if ($r ===
false)
return;
9223 if ($filter->post) {
9224 $this->postFilters[$filter->name] = $filter;
9226 $this->filters[$filter->name] = $filter;
9238 foreach ($this->registeredFilters
as $name => $filter) {
9239 if ($filter->always_load) {
9242 $conf =
$config->get(
'URI.' . $name);
9243 if ($conf !==
false && $conf !==
null) {
9248 unset($this->registeredFilters);
9253 $this->host =
$config->get(
'URI.Host');
9254 $base_uri =
$config->get(
'URI.Base');
9255 if (!is_null($base_uri)) {
9257 $this->base = $parser->parse($base_uri);
9258 $this->defaultScheme = $this->base->scheme;
9259 if (is_null($this->host)) $this->host = $this->base->host;
9261 if (is_null($this->defaultScheme)) $this->defaultScheme =
$config->get(
'URI.DefaultScheme');
9271 foreach ($this->filters
as $name =>
$f) {
9272 $result =
$f->filter($uri,
$config, $context);
9273 if (!$result)
return false;
9280 foreach ($this->postFilters
as $name =>
$f) {
9281 $result =
$f->filter($uri,
$config, $context);
9282 if (!$result)
return false;
9393 $uri = $this->percentEncoder->normalize($uri);
9399 '(([a-zA-Z0-9\.\+\-]+):)?'.
9400 '(//([^/?#"<>]*))?'.
9407 $result = preg_match($r_URI, $uri, $matches);
9409 if (!$result)
return false;
9412 $scheme = !
empty($matches[1]) ? $matches[2] :
null;
9413 $authority = !
empty($matches[3]) ? $matches[4] :
null;
9414 $path = $matches[5];
9415 $query = !
empty($matches[6]) ? $matches[7] :
null;
9416 $fragment = !
empty($matches[8]) ? $matches[9] :
null;
9419 if ($authority !==
null) {
9420 $r_authority =
"/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
9422 preg_match($r_authority, $authority, $matches);
9423 $userinfo = !
empty($matches[1]) ? $matches[2] :
null;
9424 $host = !
empty($matches[3]) ? $matches[3] :
'';
9425 $port = !
empty($matches[4]) ? (int) $matches[5] :
null;
9427 $port = $host = $userinfo =
null;
9431 $scheme, $userinfo, $host, $port,
$path, $query, $fragment);
9502 if ($this->default_port == $uri->port) {
9507 if (!$this->may_omit_host &&
9509 (!is_null($uri->scheme) && ($uri->host ===
'' || is_null($uri->host))) ||
9513 (is_null($uri->scheme) && $uri->host ===
'')
9516 if (is_null($uri->scheme)) {
9517 if (substr($uri->path, 0, 2) !=
'//') {
9526 $host =
$config->get(
'URI.Host');
9527 if (!is_null($host)) {
9559 static $instance =
null;
9560 if ($prototype !==
null) {
9561 $instance = $prototype;
9562 } elseif ($instance ===
null || $prototype ==
true) {
9588 $allowed_schemes =
$config->get(
'URI.AllowedSchemes');
9589 if (!
$config->get(
'URI.OverrideAllowedSchemes') &&
9590 !isset($allowed_schemes[$scheme])
9595 if (isset($this->
schemes[$scheme])) {
9596 return $this->
schemes[$scheme];
9598 if (!isset($allowed_schemes[$scheme])) {
9602 $class =
'HTMLPurifier_URIScheme_' . $scheme;
9603 if (!class_exists($class)) {
9606 $this->
schemes[$scheme] =
new $class();
9607 return $this->
schemes[$scheme];
9615 public function register($scheme, $scheme_obj)
9617 $this->
schemes[$scheme] = $scheme_obj;
9646 self::ENGLISH => array(
9651 self::METRIC => array(
'pt',
'0.352777778',
'mm'),
9653 self::METRIC => array(
9656 self::ENGLISH => array(
'mm',
'2.83464567',
'pt'),
9678 public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath =
false)
9680 $this->outputPrecision = $output_precision;
9681 $this->internalPrecision = $internal_precision;
9682 $this->bcmath = !$force_no_bcmath && function_exists(
'bcmul');
9706 if (!$length->isValid()) {
9710 $n = $length->getN();
9711 $unit = $length->getUnit();
9713 if ($n ===
'0' || $unit ===
false) {
9717 $state = $dest_state =
false;
9718 foreach (self::$units
as $k => $x) {
9719 if (isset($x[$unit])) {
9722 if (isset($x[$to_unit])) {
9726 if (!$state || !$dest_state) {
9733 if ($sigfigs < $this->outputPrecision) {
9741 $log = (int)floor(log(abs($n), 10));
9742 $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision;
9744 for ($i = 0; $i < 2; $i++) {
9747 if ($dest_state === $state) {
9749 $dest_unit = $to_unit;
9752 $dest_unit = self::$units[$state][$dest_state][0];
9756 if ($dest_unit !== $unit) {
9757 $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
9758 $n = $this->mul($n, $factor, $cp);
9770 if ($dest_state === $state) {
9783 $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
9784 $unit = self::$units[$state][$dest_state][2];
9785 $state = $dest_state;
9792 if ($unit !== $to_unit) {
9800 $n = $this->round($n, $sigfigs);
9801 if (strpos($n,
'.') !==
false) {
9802 $n = rtrim($n,
'0');
9804 $n = rtrim($n,
'.');
9816 $n = ltrim($n,
'0+-');
9817 $dp = strpos($n,
'.');
9818 if ($dp ===
false) {
9819 $sigfigs = strlen(rtrim($n,
'0'));
9821 $sigfigs = strlen(ltrim($n,
'0.'));
9836 private function add($s1, $s2, $scale)
9838 if ($this->bcmath) {
9839 return bcadd($s1, $s2, $scale);
9841 return $this->scale((
float)$s1 + (
float)$s2, $scale);
9852 private function mul($s1, $s2, $scale)
9854 if ($this->bcmath) {
9855 return bcmul($s1, $s2, $scale);
9857 return $this->scale((
float)$s1 * (
float)$s2, $scale);
9868 private function div($s1, $s2, $scale)
9870 if ($this->bcmath) {
9871 return bcdiv($s1, $s2, $scale);
9873 return $this->scale((
float)$s1 / (
float)$s2, $scale);
9884 private function round($n, $sigfigs)
9886 $new_log = (int)floor(log(abs($n), 10));
9887 $rp = $sigfigs - $new_log - 1;
9888 $neg = $n < 0 ?
'-' :
'';
9889 if ($this->bcmath) {
9891 $n = bcadd($n, $neg .
'0.' . str_repeat(
'0', $rp) .
'5', $rp + 1);
9892 $n = bcdiv($n,
'1', $rp);
9896 $n = bcadd($n, $neg .
'5' . str_repeat(
'0', $new_log - $sigfigs), 0);
9897 $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat(
'0', $new_log - $sigfigs + 1);
9901 return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
9911 private function scale($r, $scale)
9916 $r = sprintf(
'%.0f', (
float)$r);
9921 $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);
9923 return substr($precise, 0, -1) . str_repeat(
'0', -$scale + 1);
9925 return sprintf(
'%.' . $scale .
'f', (
float)$r);
9957 'string' => self::C_STRING,
9958 'istring' => self::ISTRING,
9959 'text' => self::TEXT,
9960 'itext' => self::ITEXT,
9961 'int' => self::C_INT,
9962 'float' => self::C_FLOAT,
9963 'bool' => self::C_BOOL,
9964 'lookup' => self::LOOKUP,
9965 'list' => self::ALIST,
9966 'hash' => self::HASH,
9967 'mixed' => self::C_MIXED
9975 self::C_STRING =>
true,
9976 self::ISTRING =>
true,
9978 self::ITEXT =>
true,
9991 final public function parse($var, $type, $allow_null =
false)
9993 if (is_string($type)) {
10001 if ($allow_null && $var ===
null) {
10007 case (self::C_STRING):
10008 case (self::ISTRING):
10010 case (self::ITEXT):
10011 if (!is_string($var)) {
10014 if ($type == self::ISTRING || $type == self::ITEXT) {
10015 $var = strtolower($var);
10018 case (self::C_INT):
10019 if (!is_int($var)) {
10023 case (self::C_FLOAT):
10024 if (!is_float($var)) {
10028 case (self::C_BOOL):
10029 if (!is_bool($var)) {
10033 case (self::LOOKUP):
10034 case (self::ALIST):
10036 if (!is_array($var)) {
10039 if ($type === self::LOOKUP) {
10040 foreach ($var
as $k) {
10042 $this->
error(
'Lookup table contains value other than true');
10045 } elseif ($type === self::ALIST) {
10046 $keys = array_keys($var);
10047 if (array_keys($keys) !== $keys) {
10048 $this->
error(
'Indices for list are not uniform');
10052 case (self::C_MIXED):
10106 $vtype = gettype($var);
10121 if (!isset($lookup[$type])) {
10124 return $lookup[$type];
10168 $this->back =
$back;
10178 $z =
new self(array(), array_reverse($array));
10180 return array($z, $t);
10190 if ($t !== NULL) $a[] = $t;
10191 for ($i = count($this->back)-1; $i >= 0; $i--) {
10192 $a[] = $this->back[$i];
10203 if ($t !== NULL) array_push($this->front, $t);
10204 return empty($this->back) ? NULL : array_pop($this->back);
10214 for ($i = 0; $i < $n; $i++) {
10215 $t = $this->
next($t);
10226 if ($t !== NULL) array_push($this->back, $t);
10227 return empty($this->front) ? NULL : array_pop($this->front);
10235 public function delete() {
10236 return empty($this->back) ? NULL : array_pop($this->back);
10244 return empty($this->back);
10252 if ($t !== NULL) array_push($this->front, $t);
10260 if ($t !== NULL) array_push($this->back, $t);
10283 public function splice($t, $delete, $replacement) {
10287 for ($i = $delete; $i > 0; $i--) {
10289 $r = $this->
delete();
10292 for ($i = count($replacement)-1; $i >= 0; $i--) {
10294 $r = $replacement[$i];
10296 return array($old, $r);
10326 $definition =
$config->getCSSDefinition();
10327 $allow_duplicates =
$config->get(
"CSS.AllowDuplicates");
10334 $len = strlen($css);
10336 $declarations = array();
10338 for ($i = 0; $i < $len; $i++) {
10339 $c = strcspn($css,
";'\"", $i);
10340 $accum .= substr($css, $i, $c);
10342 if ($i == $len)
break;
10346 if ($d == $quoted) {
10351 $declarations[] = $accum;
10359 if ($accum !=
"") $declarations[] = $accum;
10361 $propvalues = array();
10362 $new_declarations =
'';
10368 $context->register(
'CurrentCSSProperty', $property);
10370 foreach ($declarations
as $declaration) {
10371 if (!$declaration) {
10374 if (!strpos($declaration,
':')) {
10377 list($property, $value) = explode(
':', $declaration, 2);
10378 $property = trim($property);
10379 $value = trim($value);
10382 if (isset($definition->info[$property])) {
10386 if (ctype_lower($property)) {
10389 $property = strtolower($property);
10390 if (isset($definition->info[$property])) {
10399 if (strtolower(trim($value)) !==
'inherit') {
10401 $result = $definition->info[$property]->validate(
10407 $result =
'inherit';
10409 if ($result ===
false) {
10412 if ($allow_duplicates) {
10413 $new_declarations .=
"$property:$result;";
10415 $propvalues[$property] = $result;
10419 $context->destroy(
'CurrentCSSProperty');
10425 foreach ($propvalues
as $prop => $value) {
10426 $new_declarations .=
"$prop:$value;";
10429 return $new_declarations ? $new_declarations :
false;
10467 return $this->clone->validate($v,
$config, $context);
10525 $string = trim($string);
10526 if (!$this->case_sensitive) {
10528 $string = ctype_lower($string) ? $string : strtolower($string);
10530 $result = isset($this->valid_values[$string]);
10532 return $result ? $string :
false;
10543 if (strlen($string) > 2 && $string[0] ==
's' && $string[1] ==
':') {
10544 $string = substr($string, 2);
10547 $sensitive =
false;
10549 $values = explode(
',', $string);
10594 $this->zero =
$zero;
10607 if ($integer ===
'') {
10615 if ($this->negative && $integer[0] ===
'-') {
10616 $digits = substr($integer, 1);
10617 if ($digits ===
'0') {
10620 } elseif ($this->positive && $integer[0] ===
'+') {
10621 $digits = $integer = substr($integer, 1);
10623 $digits = $integer;
10627 if (!ctype_digit($digits)) {
10632 if (!$this->zero && $integer == 0) {
10635 if (!$this->positive && $integer > 0) {
10638 if (!$this->negative && $integer < 0) {
10665 $string = trim($string);
10670 $subtags = explode(
'-', $string);
10671 $num_subtags = count($subtags);
10673 if ($num_subtags == 0) {
10678 $length = strlen($subtags[0]);
10683 if (!($subtags[0] ==
'x' || $subtags[0] ==
'i')) {
10689 if (!ctype_alpha($subtags[0])) {
10691 } elseif (!ctype_lower($subtags[0])) {
10692 $subtags[0] = strtolower($subtags[0]);
10699 $new_string = $subtags[0];
10700 if ($num_subtags == 1) {
10701 return $new_string;
10705 $length = strlen($subtags[1]);
10706 if ($length == 0 || ($length == 1 && $subtags[1] !=
'x') || $length > 8 || !ctype_alnum($subtags[1])) {
10707 return $new_string;
10709 if (!ctype_lower($subtags[1])) {
10710 $subtags[1] = strtolower($subtags[1]);
10713 $new_string .=
'-' . $subtags[1];
10714 if ($num_subtags == 2) {
10715 return $new_string;
10719 for ($i = 2; $i < $num_subtags; $i++) {
10720 $length = strlen($subtags[$i]);
10721 if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
10722 return $new_string;
10724 if (!ctype_lower($subtags[$i])) {
10725 $subtags[$i] = strtolower($subtags[$i]);
10727 $new_string .=
'-' . $subtags[$i];
10729 return $new_string;
10766 $this->withTag = $with_tag;
10767 $this->withoutTag = $without_tag;
10778 $token = $context->get(
'CurrentToken',
true);
10779 if (!$token || $token->name !== $this->tag) {
10780 return $this->withoutTag->validate($string,
$config, $context);
10782 return $this->withTag->validate($string,
$config, $context);
10836 $this->embedsResource = (bool)$embeds_resource;
10845 $embeds = ($string ===
'embedded');
10857 if (
$config->get(
'URI.Disable')) {
10864 $uri = $this->parser->parse($uri);
10865 if ($uri ===
false) {
10870 $context->register(
'EmbeddedURI', $this->embedsResource);
10876 $result = $uri->validate(
$config, $context);
10882 $uri_def =
$config->getDefinition(
'URI');
10883 $result = $uri_def->filter($uri,
$config, $context);
10889 $scheme_obj = $uri->getSchemeObj(
$config, $context);
10890 if (!$scheme_obj) {
10893 if ($this->embedsResource && !$scheme_obj->browsable) {
10896 $result = $scheme_obj->validate($uri,
$config, $context);
10902 $result = $uri_def->postFilter($uri,
$config, $context);
10912 $context->destroy(
'EmbeddedURI');
10917 return $uri->toString();
10957 if ($number ===
'') {
10960 if ($number ===
'0') {
10965 switch ($number[0]) {
10967 if ($this->non_negative) {
10972 $number = substr($number, 1);
10975 if (ctype_digit($number)) {
10976 $number = ltrim($number,
'0');
10977 return $number ? $sign . $number :
'0';
10981 if (strpos($number,
'.') ===
false) {
10985 list($left, $right) = explode(
'.', $number, 2);
10987 if ($left ===
'' && $right ===
'') {
10990 if ($left !==
'' && !ctype_digit($left)) {
10994 $left = ltrim($left,
'0');
10995 $right = rtrim($right,
'0');
10997 if ($right ===
'') {
10998 return $left ? $sign . $left :
'0';
10999 } elseif (!ctype_digit($right)) {
11002 return $sign . $left .
'.' . $right;
11015 parent::__construct(
false);
11026 $result = parent::validate($number,
$config, $context);
11027 if ($result ===
false) {
11030 $float = (float)$result;
11031 if ($float < 0.0) {
11034 if ($float > 1.0) {
11065 $this->info[
'background-color'] =
$def->info[
'background-color'];
11066 $this->info[
'background-image'] =
$def->info[
'background-image'];
11067 $this->info[
'background-repeat'] =
$def->info[
'background-repeat'];
11068 $this->info[
'background-attachment'] =
$def->info[
'background-attachment'];
11069 $this->info[
'background-position'] =
$def->info[
'background-position'];
11082 if ($string ===
'') {
11087 $string = $this->
mungeRgb($string);
11090 $bits = explode(
' ', $string);
11093 $caught[
'color'] =
false;
11094 $caught[
'image'] =
false;
11095 $caught[
'repeat'] =
false;
11096 $caught[
'attachment'] =
false;
11097 $caught[
'position'] =
false;
11101 foreach ($bits
as $bit) {
11105 foreach ($caught
as $key => $status) {
11106 if ($key !=
'position') {
11107 if ($status !==
false) {
11110 $r = $this->info[
'background-' . $key]->validate($bit,
$config, $context);
11114 if ($r ===
false) {
11117 if ($key ==
'position') {
11118 if ($caught[$key] ===
false) {
11119 $caught[$key] =
'';
11121 $caught[$key] .= $r .
' ';
11123 $caught[$key] = $r;
11133 if ($caught[
'position'] !==
false) {
11134 $caught[
'position'] = $this->info[
'background-position']->
11139 foreach ($caught
as $value) {
11140 if ($value ===
false) {
11149 return implode(
' ',
$ret);
11226 $bits = explode(
' ', $string);
11228 $keywords = array();
11229 $keywords[
'h'] =
false;
11230 $keywords[
'v'] =
false;
11231 $keywords[
'ch'] =
false;
11232 $keywords[
'cv'] =
false;
11233 $measures = array();
11245 foreach ($bits
as $bit) {
11251 $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
11252 if (isset($lookup[$lbit])) {
11253 $status = $lookup[$lbit];
11254 if ($status ==
'c') {
11261 $keywords[$status] = $lbit;
11266 $r = $this->length->validate($bit,
$config, $context);
11267 if ($r !==
false) {
11273 $r = $this->percentage->validate($bit,
$config, $context);
11274 if ($r !==
false) {
11287 if ($keywords[
'h']) {
11288 $ret[] = $keywords[
'h'];
11289 } elseif ($keywords[
'ch']) {
11290 $ret[] = $keywords[
'ch'];
11291 $keywords[
'cv'] =
false;
11292 } elseif (count($measures)) {
11293 $ret[] = array_shift($measures);
11296 if ($keywords[
'v']) {
11297 $ret[] = $keywords[
'v'];
11298 } elseif ($keywords[
'cv']) {
11299 $ret[] = $keywords[
'cv'];
11300 } elseif (count($measures)) {
11301 $ret[] = array_shift($measures);
11307 return implode(
' ',
$ret);
11333 $this->info[
'border-width'] =
$def->info[
'border-width'];
11334 $this->info[
'border-style'] =
$def->info[
'border-style'];
11335 $this->info[
'border-top-color'] =
$def->info[
'border-top-color'];
11347 $string = $this->
mungeRgb($string);
11348 $bits = explode(
' ', $string);
11351 foreach ($bits
as $bit) {
11352 foreach ($this->info
as $propname => $validator) {
11353 if (isset($done[$propname])) {
11356 $r = $validator->validate($bit,
$config, $context);
11357 if ($r !==
false) {
11359 $done[$propname] =
true;
11364 return rtrim(
$ret);
11396 static $colors =
null;
11397 if ($colors ===
null) {
11398 $colors =
$config->get(
'Core.ColorKeywords');
11401 $color = trim($color);
11402 if ($color ===
'') {
11406 $lower = strtolower($color);
11407 if (isset($colors[$lower])) {
11408 return $colors[$lower];
11411 if (preg_match(
'#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
11412 $length = strlen($color);
11413 if (strpos($color,
')') !== $length - 1) {
11418 $function = $matches[1];
11420 $parameters_size = 3;
11421 $alpha_channel =
false;
11422 if (substr($function, -1) ===
'a') {
11423 $parameters_size = 4;
11424 $alpha_channel =
true;
11431 $allowed_types = array(
11432 1 => array(
'percentage' => 100,
'integer' => 255),
11433 2 => array(
'percentage' => 100,
'integer' => 255),
11434 3 => array(
'percentage' => 100,
'integer' => 255),
11436 $allow_different_types =
false;
11438 if (strpos($function,
'hsl') !==
false) {
11439 $allowed_types = array(
11440 1 => array(
'integer' => 360),
11441 2 => array(
'percentage' => 100),
11442 3 => array(
'percentage' => 100),
11444 $allow_different_types =
true;
11447 $values = trim(str_replace($function,
'', $color),
' ()');
11449 $parts = explode(
',', $values);
11450 if (count($parts) !== $parameters_size) {
11455 $new_parts = array();
11458 foreach ($parts
as $part) {
11460 $part = trim($part);
11462 if ($part ===
'') {
11467 if ($alpha_channel ===
true && $i === count($parts)) {
11468 $result = $this->alpha->validate($part,
$config, $context);
11470 if ($result ===
false) {
11474 $new_parts[] = (string)$result;
11478 if (substr($part, -1) ===
'%') {
11479 $current_type =
'percentage';
11481 $current_type =
'integer';
11484 if (!array_key_exists($current_type, $allowed_types[$i])) {
11489 $type = $current_type;
11492 if ($allow_different_types ===
false && $type != $current_type) {
11496 $max_value = $allowed_types[$i][$current_type];
11498 if ($current_type ==
'integer') {
11500 $new_parts[] = (int)max(min($part, $max_value), 0);
11501 } elseif ($current_type ==
'percentage') {
11502 $new_parts[] = (float)max(min(rtrim($part,
'%'), $max_value), 0) .
'%';
11506 $new_values = implode(
',', $new_parts);
11508 $color = $function .
'(' . $new_values .
')';
11511 if ($color[0] ===
'#') {
11512 $hex = substr($color, 1);
11515 $color =
'#' . $color;
11517 $length = strlen($hex);
11518 if ($length !== 3 && $length !== 6) {
11521 if (!ctype_xdigit($hex)) {
11558 $this->defs =
$defs;
11569 foreach ($this->defs
as $i =>
$def) {
11570 $result = $this->defs[$i]->validate($string,
$config, $context);
11571 if ($result !==
false) {
11616 $token = $context->get(
'CurrentToken',
true);
11617 if ($token && $token->name == $this->element) {
11620 return $this->def->validate($string,
$config, $context);
11654 if ($value ===
'none') {
11658 $function_length = strcspn($value,
'(');
11659 $function = trim(substr($value, 0, $function_length));
11660 if ($function !==
'alpha' &&
11661 $function !==
'Alpha' &&
11662 $function !==
'progid:DXImageTransform.Microsoft.Alpha'
11666 $cursor = $function_length + 1;
11667 $parameters_length = strcspn($value,
')', $cursor);
11668 $parameters = substr($value, $cursor, $parameters_length);
11669 $params = explode(
',', $parameters);
11670 $ret_params = array();
11672 foreach ($params
as $param) {
11673 list($key, $value) = explode(
'=', $param);
11675 $value = trim($value);
11676 if (isset($lookup[$key])) {
11679 if ($key !==
'opacity') {
11682 $value = $this->intValidator->validate($value,
$config, $context);
11683 if ($value ===
false) {
11686 $int = (int)$value;
11693 $ret_params[] =
"$key=$value";
11694 $lookup[$key] =
true;
11696 $ret_parameters = implode(
',', $ret_params);
11697 $ret_function =
"$function($ret_parameters)";
11698 return $ret_function;
11728 $this->info[
'font-style'] =
$def->info[
'font-style'];
11729 $this->info[
'font-variant'] =
$def->info[
'font-variant'];
11730 $this->info[
'font-weight'] =
$def->info[
'font-weight'];
11731 $this->info[
'font-size'] =
$def->info[
'font-size'];
11732 $this->info[
'line-height'] =
$def->info[
'line-height'];
11733 $this->info[
'font-family'] =
$def->info[
'font-family'];
11744 static $system_fonts = array(
11748 'message-box' =>
true,
11749 'small-caption' =>
true,
11750 'status-bar' =>
true
11755 if ($string ===
'') {
11760 $lowercase_string = strtolower($string);
11761 if (isset($system_fonts[$lowercase_string])) {
11762 return $lowercase_string;
11765 $bits = explode(
' ', $string);
11768 $stage_1 = array(
'font-style',
'font-variant',
'font-weight');
11771 for ($i = 0, $size = count($bits); $i < $size; $i++) {
11772 if ($bits[$i] ===
'') {
11777 foreach ($stage_1
as $validator_name) {
11778 if (isset($caught[$validator_name])) {
11781 $r = $this->info[$validator_name]->validate(
11786 if ($r !==
false) {
11787 $final .= $r .
' ';
11788 $caught[$validator_name] =
true;
11793 if (count($caught) >= 3) {
11796 if ($r !==
false) {
11800 $found_slash =
false;
11801 if (strpos($bits[$i],
'/') !==
false) {
11802 list($font_size, $line_height) =
11803 explode(
'/', $bits[$i]);
11804 if ($line_height ===
'') {
11806 $line_height =
false;
11807 $found_slash =
true;
11810 $font_size = $bits[$i];
11811 $line_height =
false;
11813 $r = $this->info[
'font-size']->validate(
11818 if ($r !==
false) {
11821 if ($line_height ===
false) {
11823 for ($j = $i + 1; $j < $size; $j++) {
11824 if ($bits[$j] ===
'') {
11827 if ($bits[$j] ===
'/') {
11828 if ($found_slash) {
11831 $found_slash =
true;
11835 $line_height = $bits[$j];
11840 $found_slash =
true;
11843 if ($found_slash) {
11845 $r = $this->info[
'line-height']->validate(
11850 if ($r !==
false) {
11851 $final .=
'/' . $r;
11861 implode(
' ', array_slice($bits, $i, $size - $i));
11862 $r = $this->info[
'font-family']->validate(
11867 if ($r !==
false) {
11868 $final .= $r .
' ';
11870 return rtrim($final);
11893 $this->mask =
'_- ';
11894 for ($c =
'a'; $c <=
'z'; $c++) {
11897 for ($c =
'A'; $c <=
'Z'; $c++) {
11900 for ($c =
'0'; $c <=
'9'; $c++) {
11904 for ($i = 0x80; $i <= 0xFF; $i++) {
11908 $this->mask .= chr($i);
11937 static $generic_names = array(
11939 'sans-serif' =>
true,
11940 'monospace' =>
true,
11944 $allowed_fonts =
$config->get(
'CSS.AllowedFonts');
11947 $fonts = explode(
',', $string);
11949 foreach ($fonts
as $font) {
11950 $font = trim($font);
11951 if ($font ===
'') {
11955 if (isset($generic_names[$font])) {
11956 if ($allowed_fonts ===
null || isset($allowed_fonts[$font])) {
11957 $final .= $font .
', ';
11962 if ($font[0] ===
'"' || $font[0] ===
"'") {
11963 $length = strlen($font);
11964 if ($length <= 2) {
11968 if ($font[$length - 1] !== $quote) {
11971 $font = substr($font, 1, $length - 2);
11978 if ($allowed_fonts !==
null && !isset($allowed_fonts[$font])) {
11982 if (ctype_alnum($font) && $font !==
'') {
11984 $final .= $font .
', ';
11990 $font = str_replace(array(
"\n",
"\t",
"\r",
"\x0C"),
' ', $font);
12071 if (strspn($font, $this->mask) !== strlen($font)) {
12088 $final .=
"'$font', ";
12090 $final = rtrim($final,
', ');
12091 if ($final ===
'') {
12117 $string = trim($string);
12124 $pattern =
'/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
12125 if (!preg_match($pattern, $string)) {
12170 $string = trim($string);
12171 $is_important =
false;
12173 if (strlen($string) >= 9 && substr($string, -9) ===
'important') {
12174 $temp = rtrim(substr($string, 0, -9));
12176 if (strlen($temp) >= 1 && substr($temp, -1) ===
'!') {
12177 $string = rtrim(substr($temp, 0, -1));
12178 $is_important =
true;
12181 $string = $this->def->validate($string,
$config, $context);
12182 if ($this->allow && $is_important) {
12183 $string .=
' !important';
12230 if ($string ===
'') {
12233 if ($string ===
'0') {
12236 if (strlen($string) === 1) {
12241 if (!$length->isValid()) {
12246 $c = $length->compareTo($this->min);
12247 if ($c ===
false) {
12255 $c = $length->compareTo($this->max);
12256 if ($c ===
false) {
12263 return $length->toString();
12291 $this->info[
'list-style-type'] =
$def->info[
'list-style-type'];
12292 $this->info[
'list-style-position'] =
$def->info[
'list-style-position'];
12293 $this->info[
'list-style-image'] =
$def->info[
'list-style-image'];
12306 if ($string ===
'') {
12311 $bits = explode(
' ', strtolower($string));
12314 $caught[
'type'] =
false;
12315 $caught[
'position'] =
false;
12316 $caught[
'image'] =
false;
12321 foreach ($bits
as $bit) {
12328 foreach ($caught
as $key => $status) {
12329 if ($status !==
false) {
12332 $r = $this->info[
'list-style-' . $key]->validate($bit,
$config, $context);
12333 if ($r ===
false) {
12336 if ($r ===
'none') {
12342 if ($key ==
'image') {
12346 $caught[$key] = $r;
12359 if ($caught[
'type']) {
12360 $ret[] = $caught[
'type'];
12364 if ($caught[
'image']) {
12365 $ret[] = $caught[
'image'];
12369 if ($caught[
'position']) {
12370 $ret[] = $caught[
'position'];
12376 return implode(
' ',
$ret);
12429 if ($string ===
'') {
12432 $parts = explode(
' ', $string);
12433 $length = count($parts);
12435 for ($i = 0, $num = 0; $i < $length && $num <
$this->max; $i++) {
12436 if (ctype_space($parts[$i])) {
12439 $result = $this->single->validate($parts[$i],
$config, $context);
12440 if ($result !==
false) {
12441 $final .= $result .
' ';
12445 if ($final ===
'') {
12448 return rtrim($final);
12486 if ($string ===
'') {
12489 $length = strlen($string);
12490 if ($length === 1) {
12493 if ($string[$length - 1] !==
'%') {
12497 $number = substr($string, 0, $length - 1);
12498 $number = $this->number_def->validate($number,
$config, $context);
12500 if ($number ===
false) {
12527 static $allowed_values = array(
12528 'line-through' =>
true,
12529 'overline' =>
true,
12530 'underline' =>
true,
12533 $string = strtolower($this->
parseCDATA($string));
12535 if ($string ===
'none') {
12539 $parts = explode(
' ', $string);
12541 foreach ($parts
as $part) {
12542 if (isset($allowed_values[$part])) {
12543 $final .= $part .
' ';
12546 $final = rtrim($final);
12547 if ($final ===
'') {
12572 parent::__construct(
true);
12586 $uri_string = $this->
parseCDATA($uri_string);
12587 if (strpos($uri_string,
'url(') !== 0) {
12590 $uri_string = substr($uri_string, 4);
12591 if (strlen($uri_string) == 0) {
12594 $new_length = strlen($uri_string) - 1;
12595 if ($uri_string[$new_length] !=
')') {
12598 $uri = trim(substr($uri_string, 0, $new_length));
12600 if (!
empty($uri) && ($uri[0] ==
"'" || $uri[0] ==
'"')) {
12602 $new_length = strlen($uri) - 1;
12603 if ($uri[$new_length] !== $quote) {
12606 $uri = substr($uri, 1, $new_length - 1);
12611 $result = parent::validate($uri,
$config, $context);
12613 if ($result ===
false) {
12618 $result = str_replace(array(
'"',
"\\",
"\n",
"\x0c",
"\r"),
"", $result);
12622 $result = str_replace(array(
'(',
')',
"'"), array(
'%28',
'%29',
'%27'), $result);
12628 return "url(\"$result\")";
12699 $string = trim($string);
12708 if (
empty($tokens)) {
12711 return implode(
' ', $tokens);
12730 $pattern =
'/(?:(?<=\s)|\A)' .
12731 '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' .
12733 preg_match_all($pattern, $string, $matches);
12734 return $matches[1];
12770 $name =
$config->getDefinition(
'HTML')->doctype->name;
12771 if ($name ==
"XHTML 1.1" || $name ==
"XHTML 2.0") {
12772 return parent::split($string,
$config, $context);
12774 return preg_split(
'/\s+/', $string);
12786 $allowed =
$config->get(
'Attr.AllowedClasses');
12787 $forbidden =
$config->get(
'Attr.ForbiddenClasses');
12789 foreach ($tokens
as $token) {
12790 if (($allowed ===
null || isset($allowed[$token])) &&
12791 !isset($forbidden[$token]) &&
12794 !in_array($token,
$ret,
true)
12819 static $colors =
null;
12820 if ($colors ===
null) {
12821 $colors =
$config->get(
'Core.ColorKeywords');
12824 $string = trim($string);
12826 if (
empty($string)) {
12829 $lower = strtolower($string);
12830 if (isset($colors[$lower])) {
12831 return $colors[$lower];
12833 if ($string[0] ===
'#') {
12834 $hex = substr($string, 1);
12839 $length = strlen($hex);
12840 if ($length !== 3 && $length !== 6) {
12843 if (!ctype_xdigit($hex)) {
12846 if ($length === 3) {
12847 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
12885 if ($this->valid_values ===
false) {
12886 $this->valid_values =
$config->get(
'Attr.AllowedFrameTargets');
12888 return parent::validate($string,
$config, $context);
12944 $prefix =
$config->get(
'Attr.IDPrefix');
12945 if ($prefix !==
'') {
12946 $prefix .=
$config->get(
'Attr.IDPrefixLocal');
12948 if (strpos($id, $prefix) !== 0) {
12949 $id = $prefix . $id;
12951 } elseif (
$config->get(
'Attr.IDPrefixLocal') !==
'') {
12953 '%Attr.IDPrefixLocal cannot be used unless ' .
12954 '%Attr.IDPrefix is set',
12960 $id_accumulator =& $context->get(
'IDAccumulator');
12961 if (isset($id_accumulator->ids[$id])) {
12968 if (
$config->get(
'Attr.ID.HTML5') ===
true) {
12969 if (preg_match(
'/[\t\n\x0b\x0c ]/', $id)) {
12973 if (ctype_alpha($id)) {
12976 if (!ctype_alpha(@$id[0])) {
12984 if ($trim !==
'') {
12990 $regexp =
$config->get(
'Attr.IDBlacklistRegexp');
12991 if ($regexp && preg_match($regexp, $id)) {
12996 $id_accumulator->add($id);
13037 $string = trim($string);
13038 if ($string ===
'0') {
13041 if ($string ===
'') {
13044 $length = strlen($string);
13045 if (substr($string, $length - 2) ==
'px') {
13046 $string = substr($string, 0, $length - 2);
13048 if (!is_numeric($string)) {
13051 $int = (int)$string;
13061 if ($this->max !==
null && $int > $this->max) {
13064 return (
string)$int;
13073 if ($string ===
'') {
13076 $max = (int)$string;
13078 $class = get_class($this);
13079 return new $class(
$max);
13105 $string = trim($string);
13106 if ($string ===
'') {
13110 $parent_result = parent::validate($string,
$config, $context);
13111 if ($parent_result !==
false) {
13112 return $parent_result;
13115 $length = strlen($string);
13116 $last_char = $string[$length - 1];
13118 if ($last_char !==
'%') {
13122 $points = substr($string, 0, $length - 1);
13124 if (!is_numeric($points)) {
13128 $points = (int)$points;
13133 if ($points > 100) {
13136 return ((
string)$points) .
'%';
13164 $configLookup = array(
13165 'rel' =>
'AllowedRel',
13166 'rev' =>
'AllowedRev'
13168 if (!isset($configLookup[
$name])) {
13170 'Unrecognized attribute name for link ' .
13188 if (
empty($allowed)) {
13193 $parts = explode(
' ', $string);
13196 $ret_lookup = array();
13197 foreach ($parts
as $part) {
13198 $part = strtolower(trim($part));
13199 if (!isset($allowed[$part])) {
13202 $ret_lookup[$part] =
true;
13205 if (
empty($ret_lookup)) {
13208 $string = implode(
' ', array_keys($ret_lookup));
13234 $string = trim($string);
13235 if ($string ===
'') {
13239 $parent_result = parent::validate($string,
$config, $context);
13240 if ($parent_result !==
false) {
13241 return $parent_result;
13244 $length = strlen($string);
13245 $last_char = $string[$length - 1];
13247 if ($last_char !==
'*') {
13251 $int = substr($string, 0, $length - 1);
13256 if (!is_numeric($int)) {
13270 return ((
string)$int) .
'*';
13331 $length = strlen($string);
13338 if ($string ===
'') {
13341 if ($length > 1 && $string[0] ===
'[' && $string[$length - 1] ===
']') {
13343 $ip = substr($string, 1, $length - 2);
13344 $valid = $this->ipv6->validate($ip,
$config, $context);
13345 if ($valid ===
false) {
13348 return '[' . $valid .
']';
13352 $ipv4 = $this->ipv4->validate($string,
$config, $context);
13353 if (
$ipv4 !==
false) {
13373 $underscore =
$config->get(
'Core.AllowHostnameUnderscore') ?
'_' :
'';
13380 $and =
"[a-z0-9-$underscore]";
13382 $domainlabel =
"$an(?:$and*$an)?";
13386 $toplabel =
"$an(?:$and*$an)?";
13388 if (preg_match(
"/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) {
13389 if (!ctype_digit($matches[1])) {
13395 if (function_exists(
'idn_to_ascii')) {
13396 if (defined(
'IDNA_NONTRANSITIONAL_TO_ASCII') && defined(
'INTL_IDNA_VARIANT_UTS46')) {
13397 $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
13399 $string = idn_to_ascii($string);
13405 } elseif (
$config->get(
'Core.EnableIDNA')) {
13406 $idna =
new Net_IDNA2(array(
'encoding' =>
'utf8',
'overlong' =>
false,
'strict' =>
true));
13408 $parts = explode(
'.', $string);
13410 $new_parts = array();
13411 foreach ($parts
as $part) {
13412 $encodable =
false;
13413 for ($i = 0, $c = strlen($part); $i < $c; $i++) {
13414 if (ord($part[$i]) > 0x7a) {
13420 $new_parts[] = $part;
13422 $new_parts[] = $idna->encode($part);
13425 $string = implode(
'.', $new_parts);
13426 }
catch (Exception $e) {
13431 if (preg_match(
"/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
13467 if (preg_match(
'#^' . $this->ip4 .
'$#s', $aIP)) {
13479 $oct =
'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])';
13480 $this->ip4 =
"(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
13511 $hex =
'[0-9a-fA-F]';
13512 $blk =
'(?:' . $hex .
'{1,4})';
13513 $pre =
'(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))';
13516 if (strpos($aIP,
'/') !==
false) {
13517 if (preg_match(
'#' . $pre .
'$#s', $aIP, $find)) {
13518 $aIP = substr($aIP, 0, 0 - strlen($find[0]));
13526 if (preg_match(
'#(?<=:' .
')' . $this->ip4 .
'$#s', $aIP, $find)) {
13527 $aIP = substr($aIP, 0, 0 - strlen($find[0]));
13528 $ip = explode(
'.', $find[0]);
13529 $ip = array_map(
'dechex', $ip);
13530 $aIP .= $ip[0] . $ip[1] .
':' . $ip[2] . $ip[3];
13535 $aIP = explode(
'::', $aIP);
13539 } elseif ($c == 2) {
13540 list($first, $second) = $aIP;
13541 $first = explode(
':', $first);
13542 $second = explode(
':', $second);
13544 if (count($first) + count($second) > 8) {
13548 while (count($first) < 8) {
13549 array_push($first,
'0');
13552 array_splice($first, 8 - count($second), 8, $second);
13554 unset($first, $second);
13556 $aIP = explode(
':', $aIP[0]);
13565 foreach ($aIP
as $piece) {
13566 if (!preg_match(
'#^[0-9a-fA-F]{4}$#s', sprintf(
'%04s', $piece))) {
13595 if ($string ==
'') {
13598 $string = trim($string);
13599 $result = preg_match(
'/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
13600 return $result ? $string :
false;
13621 if (!isset($attr[
'background'])) {
13628 $this->
prependCSS($attr,
"background-image:url($background);");
13653 if (isset($attr[
'dir'])) {
13656 $attr[
'dir'] =
$config->get(
'Attr.DefaultTextDir');
13678 if (!isset($attr[
'bgcolor'])) {
13685 $this->
prependCSS($attr,
"background-color:$bgcolor;");
13717 $this->attr =
$attr;
13729 if (!isset(
$attr[$this->attr])) {
13732 unset(
$attr[$this->attr]);
13755 if (!isset($attr[
'border'])) {
13760 $this->
prependCSS($attr,
"border:{$border_width}px solid;");
13802 $this->attr =
$attr;
13803 $this->enumToCSS = $enum_to_css;
13804 $this->caseSensitive = (bool)$case_sensitive;
13815 if (!isset(
$attr[$this->attr])) {
13819 $value = trim(
$attr[$this->attr]);
13820 unset(
$attr[$this->attr]);
13822 if (!$this->caseSensitive) {
13823 $value = strtolower($value);
13826 if (!isset($this->enumToCSS[$value])) {
13858 if (!isset($attr[
'src'])) {
13859 if (
$config->get(
'Core.RemoveInvalidImg')) {
13862 $attr[
'src'] =
$config->get(
'Attr.DefaultInvalidImage');
13866 if (!isset($attr[
'alt'])) {
13868 $alt =
$config->get(
'Attr.DefaultImageAlt');
13869 if ($alt ===
null) {
13870 $attr[
'alt'] = basename($attr[
'src']);
13872 $attr[
'alt'] = $alt;
13875 $attr[
'alt'] =
$config->get(
'Attr.DefaultInvalidImageAlt');
13900 'hspace' => array(
'left',
'right'),
13901 'vspace' => array(
'top',
'bottom')
13909 $this->attr =
$attr;
13910 if (!isset($this->css[
$attr])) {
13911 trigger_error(htmlspecialchars(
$attr) .
' is not valid space attribute');
13923 if (!isset(
$attr[$this->attr])) {
13930 if (!isset($this->css[$this->attr])) {
13935 foreach ($this->css[$this->attr]
as $suffix) {
13936 $property =
"margin-$suffix";
13937 $style .=
"$property:{$width}px;";
13972 if (!isset($attr[
'type'])) {
13975 $t = strtolower($attr[
'type']);
13977 if (isset($attr[
'checked']) && $t !==
'radio' && $t !==
'checkbox') {
13978 unset($attr[
'checked']);
13980 if (isset($attr[
'maxlength']) && $t !==
'text' && $t !==
'password') {
13981 unset($attr[
'maxlength']);
13983 if (isset($attr[
'size']) && $t !==
'text' && $t !==
'password') {
13984 $result = $this->pixels->validate($attr[
'size'],
$config, $context);
13985 if ($result ===
false) {
13986 unset($attr[
'size']);
13988 $attr[
'size'] = $result;
13991 if (isset($attr[
'src']) && $t !==
'image') {
13992 unset($attr[
'src']);
13994 if (!isset($attr[
'value']) && ($t ===
'radio' || $t ===
'checkbox')) {
13995 $attr[
'value'] =
'';
14021 $lang = isset($attr[
'lang']) ? $attr[
'lang'] :
false;
14022 $xml_lang = isset($attr[
'xml:lang']) ? $attr[
'xml:lang'] :
false;
14024 if ($lang !==
false && $xml_lang ===
false) {
14025 $attr[
'xml:lang'] = $lang;
14026 } elseif ($xml_lang !==
false) {
14027 $attr[
'lang'] = $xml_lang;
14056 $this->cssName = $css_name ? $css_name :
$name;
14067 if (!isset($attr[$this->
name])) {
14071 if (ctype_digit($length)) {
14074 $this->
prependCSS($attr, $this->cssName .
":$length;");
14098 if (
$config->get(
'HTML.Attr.Name.UseCDATA')) {
14101 if (!isset($attr[
'name'])) {
14105 if (isset($attr[
'id'])) {
14138 if (!isset($attr[
'name'])) {
14141 $name = $attr[
'name'];
14142 if (isset($attr[
'id']) && $attr[
'id'] === $name) {
14145 $result = $this->idDef->validate($name,
$config, $context);
14146 if ($result ===
false) {
14147 unset($attr[
'name']);
14149 $attr[
'name'] = $result;
14185 if (!isset($attr[
'href'])) {
14190 $url = $this->parser->parse($attr[
'href']);
14193 if ($scheme->browsable && !
$url->isLocal(
$config, $context)) {
14194 if (isset($attr[
'rel'])) {
14195 $rels = explode(
' ', $attr[
'rel']);
14196 if (!in_array(
'nofollow', $rels)) {
14197 $rels[] =
'nofollow';
14199 $attr[
'rel'] = implode(
' ', $rels);
14201 $attr[
'rel'] =
'nofollow';
14227 $attr[
'allowscriptaccess'] =
'never';
14228 $attr[
'allownetworking'] =
'internal';
14229 $attr[
'type'] =
'application/x-shockwave-flash';
14256 if (!isset($attr[
'type'])) {
14257 $attr[
'type'] =
'application/x-shockwave-flash';
14307 switch ($attr[
'name']) {
14310 case 'allowScriptAccess':
14311 $attr[
'value'] =
'never';
14313 case 'allowNetworking':
14314 $attr[
'value'] =
'internal';
14316 case 'allowFullScreen':
14317 if (
$config->get(
'HTML.FlashAllowFullScreen')) {
14318 $attr[
'value'] = ($attr[
'value'] ==
'true') ?
'true' :
'false';
14320 $attr[
'value'] =
'false';
14324 $attr[
'value'] = $this->wmode->validate($attr[
'value'],
$config, $context);
14328 $attr[
'name'] =
"movie";
14329 $attr[
'value'] = $this->uri->validate($attr[
'value'],
$config, $context);
14337 $attr[
'name'] = $attr[
'value'] =
null;
14360 if (!isset($attr[
'type'])) {
14361 $attr[
'type'] =
'text/javascript';
14398 if (!isset($attr[
'href'])) {
14403 $url = $this->parser->parse($attr[
'href']);
14406 if ($scheme->browsable && !
$url->isBenign(
$config, $context)) {
14407 $attr[
'target'] =
'_blank';
14436 if (isset($attr[
'rel'])) {
14437 $rels = explode(
' ', $attr[
'rel']);
14441 if (isset($attr[
'target']) && !in_array(
'noopener', $rels)) {
14442 $rels[] =
'noopener';
14444 if (!
empty($rels) || isset($attr[
'rel'])) {
14445 $attr[
'rel'] = implode(
' ', $rels);
14474 if (isset($attr[
'rel'])) {
14475 $rels = explode(
' ', $attr[
'rel']);
14479 if (isset($attr[
'target']) && !in_array(
'noreferrer', $rels)) {
14480 $rels[] =
'noreferrer';
14482 if (!
empty($rels) || isset($attr[
'rel'])) {
14483 $attr[
'rel'] = implode(
' ', $rels);
14507 if (!isset($attr[
'cols'])) {
14508 $attr[
'cols'] =
'22';
14510 if (!isset($attr[
'rows'])) {
14511 $attr[
'rows'] =
'3';
14558 $this->
elements = $this->block->elements;
14569 if ($context->get(
'IsInline') ===
false) {
14570 return $this->block->validateChildren(
14576 return $this->
inline->validateChildren(
14617 private $_pcre_regex;
14633 $raw = str_replace(
' ',
'', $this->dtd_regex);
14634 if ($raw[0] !=
'(') {
14637 $el =
'[#a-zA-Z0-9_.-]+';
14644 preg_match_all(
"/$el/", $reg, $matches);
14645 foreach ($matches[0]
as $match) {
14650 $reg = preg_replace(
"/$el/",
'(,\\0)', $reg);
14653 $reg = preg_replace(
"/([^,(|]\(+),/",
'\\1', $reg);
14656 $reg = preg_replace(
"/,\(/",
'(', $reg);
14658 $this->_pcre_regex = $reg;
14669 $list_of_children =
'';
14671 foreach ($children
as $node) {
14672 if (!
empty($node->is_whitespace)) {
14675 $list_of_children .= $node->name .
',';
14678 $list_of_children =
',' . rtrim($list_of_children,
',');
14681 '/^,?' . $this->_pcre_regex .
'$/',
14684 return (
bool)$okay;
14751 public $elements = array(
'li' =>
true,
'ul' =>
true,
'ol' =>
true);
14762 $this->whitespace =
false;
14765 if (
empty($children)) {
14770 if (!isset(
$config->getHTMLDefinition()->info[
'li'])) {
14771 trigger_error(
"Cannot allow ul/ol without allowing li", E_USER_WARNING);
14779 $all_whitespace =
true;
14781 $current_li =
null;
14783 foreach ($children
as $node) {
14784 if (!
empty($node->is_whitespace)) {
14788 $all_whitespace =
false;
14790 if ($node->name ===
'li') {
14792 $current_li = $node;
14802 if ($current_li ===
null) {
14804 $result[] = $current_li;
14806 $current_li->children[] = $node;
14807 $current_li->empty =
false;
14810 if (
empty($result)) {
14813 if ($all_whitespace) {
14851 if ($keys == array_keys($keys)) {
14882 $this->whitespace =
false;
14885 if (
empty($children)) {
14895 $pcdata_allowed = isset($this->
elements[
'#PCDATA']);
14898 $all_whitespace =
true;
14900 $stack = array_reverse($children);
14901 while (!
empty($stack)) {
14902 $node = array_pop($stack);
14903 if (!
empty($node->is_whitespace)) {
14907 $all_whitespace =
false;
14909 if (!isset($this->
elements[$node->name])) {
14919 for ($i = count($node->children) - 1; $i >= 0; $i--) {
14920 $stack[] = $node->children[$i];
14928 if (
empty($result)) {
14931 if ($all_whitespace) {
14932 $this->whitespace =
true;
14970 $result = parent::validateChildren($children,
$config, $context);
14972 if ($result ===
false) {
14973 if (
empty($children)) {
14975 } elseif ($this->whitespace) {
15043 $result = parent::validateChildren($children,
$config, $context);
15046 if ($result ===
false) {
15049 if ($result ===
true) {
15050 $result = $children;
15054 $block_wrap_name =
$def->info_block_wrapper;
15055 $block_wrap =
false;
15058 foreach ($result
as $node) {
15059 if ($block_wrap ===
false) {
15063 $ret[] = $block_wrap;
15067 $block_wrap =
false;
15072 $block_wrap->children[] = $node;
15083 private function init(
$config)
15085 if (!$this->init) {
15089 $this->fake_elements =
$def->info_content_sets[
'Flow'];
15090 $this->fake_elements[
'#PCDATA'] =
true;
15091 $this->init =
true;
15150 'colgroup' =>
true,
15166 if (
empty($children)) {
15176 $initial_ws = array();
15177 $after_caption_ws = array();
15178 $after_thead_ws = array();
15179 $after_tfoot_ws = array();
15183 $content = array();
15185 $tbody_mode =
false;
15188 $ws_accum =& $initial_ws;
15190 foreach ($children
as $node) {
15192 $ws_accum[] = $node;
15195 switch ($node->name) {
15197 $tbody_mode =
true;
15200 $content[] = $node;
15201 $ws_accum =& $content;
15205 if ($caption !==
false)
break;
15207 $ws_accum =& $after_caption_ws;
15210 $tbody_mode =
true;
15217 if ($thead ===
false) {
15219 $ws_accum =& $after_thead_ws;
15230 $node->name =
'tbody';
15231 $content[] = $node;
15232 $ws_accum =& $content;
15237 $tbody_mode =
true;
15238 if ($tfoot ===
false) {
15240 $ws_accum =& $after_tfoot_ws;
15242 $node->name =
'tbody';
15243 $content[] = $node;
15244 $ws_accum =& $content;
15250 $ws_accum =& $cols;
15257 if (!
empty($node->is_whitespace)) {
15258 $ws_accum[] = $node;
15264 if (
empty($content)) {
15268 $ret = $initial_ws;
15269 if ($caption !==
false) {
15271 $ret = array_merge(
$ret, $after_caption_ws);
15273 if ($cols !==
false) {
15276 if ($thead !==
false) {
15278 $ret = array_merge(
$ret, $after_thead_ws);
15280 if ($tfoot !==
false) {
15282 $ret = array_merge(
$ret, $after_tfoot_ws);
15287 $current_tr_tbody =
null;
15289 foreach($content
as $node) {
15290 switch ($node->name) {
15292 $current_tr_tbody =
null;
15296 if ($current_tr_tbody ===
null) {
15298 $ret[] = $current_tr_tbody;
15300 $current_tr_tbody->children[] = $node;
15304 if ($current_tr_tbody ===
null) {
15307 $current_tr_tbody->children[] = $node;
15313 $ret = array_merge(
$ret, $content);
15351 $decorator = $this->
copy();
15353 $decorator->cache =&
$cache;
15354 $decorator->type =
$cache->type;
15403 return $this->cache->get(
$config);
15412 return $this->cache->remove(
$config);
15421 return $this->cache->flush(
$config);
15430 return $this->cache->cleanup(
$config);
15529 if (file_exists($file)) {
15532 if (!$this->_prepareDir(
$config)) {
15535 return $this->_write($file, serialize(
$def),
$config);
15549 if (!$this->_prepareDir(
$config)) {
15552 return $this->_write($file, serialize(
$def),
$config);
15566 if (!file_exists($file)) {
15569 if (!$this->_prepareDir(
$config)) {
15572 return $this->_write($file, serialize(
$def),
$config);
15582 if (!file_exists($file)) {
15585 return unserialize(file_get_contents($file));
15595 if (!file_exists($file)) {
15598 return unlink($file);
15607 if (!$this->_prepareDir(
$config)) {
15611 $dh = opendir(
$dir);
15615 if (
false === $dh) {
15618 while (
false !== ($filename = readdir($dh))) {
15619 if (
empty($filename)) {
15622 if ($filename[0] ===
'.') {
15625 unlink(
$dir .
'/' . $filename);
15637 if (!$this->_prepareDir(
$config)) {
15641 $dh = opendir(
$dir);
15643 if (
false === $dh) {
15646 while (
false !== ($filename = readdir($dh))) {
15647 if (
empty($filename)) {
15650 if ($filename[0] ===
'.') {
15653 $key = substr($filename, 0, strlen($filename) - 4);
15655 unlink(
$dir .
'/' . $filename);
15697 $base =
$config->get(
'Cache.SerializerPath');
15698 $base = is_null($base) ? HTMLPURIFIER_PREFIX .
'/HTMLPurifier/DefinitionCache/Serializer' : $base;
15709 private function _write($file, $data,
$config)
15711 $result = file_put_contents($file, $data);
15712 if ($result !==
false) {
15714 $chmod =
$config->get(
'Cache.SerializerPermissions');
15715 if ($chmod !==
null) {
15716 chmod($file, $chmod & 0666);
15727 private function _prepareDir(
$config)
15730 $chmod =
$config->get(
'Cache.SerializerPermissions');
15731 if ($chmod ===
null) {
15732 if (!@mkdir($directory) && !is_dir($directory)) {
15734 'Could not create directory ' . $directory .
'',
15741 if (!is_dir($directory)) {
15743 if (!is_dir($base)) {
15745 'Base directory ' . $base .
' does not exist,
15746 please create or change using %Cache.SerializerPath',
15750 } elseif (!$this->_testPermissions($base, $chmod)) {
15753 if (!@mkdir($directory, $chmod) && !is_dir($directory)) {
15755 'Could not create directory ' . $directory .
'',
15760 if (!$this->_testPermissions($directory, $chmod)) {
15763 } elseif (!$this->_testPermissions($directory, $chmod)) {
15776 private function _testPermissions(
$dir, $chmod)
15779 if (is_writable(
$dir)) {
15782 if (!is_dir(
$dir)) {
15786 'Directory ' .
$dir .
' does not exist',
15791 if (function_exists(
'posix_getuid') && $chmod !==
null) {
15793 if (fileowner(
$dir) === posix_getuid()) {
15795 $chmod = $chmod | 0700;
15796 if (chmod(
$dir, $chmod)) {
15799 } elseif (filegroup(
$dir) === posix_getgid()) {
15800 $chmod = $chmod | 0070;
15804 $chmod = $chmod | 0777;
15807 'Directory ' .
$dir .
' not writable, ' .
15808 'please chmod to ' . decoct($chmod),
15814 'Directory ' .
$dir .
' not writable, ' .
15815 'please alter file permissions',
15980 if (isset($this->definitions[$key])) {
15981 return $this->definitions[$key];
15983 $this->definitions[$key] = parent::get(
$config);
15984 return $this->definitions[$key];
16008 'I18N' => array(
'dir' =>
false)
16020 array(
'Core',
'Lang'),
16022 'dir' =>
'Enum#ltr,rtl',
16029 $this->attr_collections[
'I18N'][
'dir'] =
'Enum#ltr,rtl';
16049 0 => array(
'Style'),
16051 'class' =>
'Class',
16053 'title' =>
'CDATA',
16057 0 => array(
'Lang'),
16060 0 => array(
'Core',
'I18N')
16086 $contents =
'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
16091 $this->
addElement(
'del',
'Inline', $contents,
'Common', $attr);
16092 $this->
addElement(
'ins',
'Inline', $contents,
'Common', $attr);
16113 if (
$def->content_model_type !=
'chameleon') {
16116 $value = explode(
'!',
$def->content_model);
16145 'Inline' =>
'Formctrl',
16156 'Required: Heading | List | Block | fieldset',
16159 'accept' =>
'ContentTypes',
16160 'accept-charset' =>
'Charsets',
16161 'action*' =>
'URI',
16162 'method' =>
'Enum#get,post',
16164 'enctype' =>
'Enum#application/x-www-form-urlencoded,multipart/form-data',
16167 $form->excludes = array(
'form' =>
true);
16175 'accept' =>
'ContentTypes',
16176 'accesskey' =>
'Character',
16178 'checked' =>
'Bool#checked',
16179 'disabled' =>
'Bool#disabled',
16180 'maxlength' =>
'Number',
16182 'readonly' =>
'Bool#readonly',
16183 'size' =>
'Number',
16184 'src' =>
'URI#embedded',
16185 'tabindex' =>
'Number',
16186 'type' =>
'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
16187 'value' =>
'CDATA',
16195 'Required: optgroup | option',
16198 'disabled' =>
'Bool#disabled',
16199 'multiple' =>
'Bool#multiple',
16201 'size' =>
'Number',
16202 'tabindex' =>
'Number',
16209 'Optional: #PCDATA',
16212 'disabled' =>
'Bool#disabled',
16214 'selected' =>
'Bool#selected',
16215 'value' =>
'CDATA',
16225 'Optional: #PCDATA',
16228 'accesskey' =>
'Character',
16229 'cols*' =>
'Number',
16230 'disabled' =>
'Bool#disabled',
16232 'readonly' =>
'Bool#readonly',
16233 'rows*' =>
'Number',
16234 'tabindex' =>
'Number',
16242 'Optional: #PCDATA | Heading | List | Block | Inline',
16245 'accesskey' =>
'Character',
16246 'disabled' =>
'Bool#disabled',
16248 'tabindex' =>
'Number',
16249 'type' =>
'Enum#button,submit,reset',
16250 'value' =>
'CDATA',
16273 $this->
addElement(
'fieldset',
'Form',
'Custom: (#WS?,legend,(Flow|#PCDATA)*)',
'Common');
16278 'Optional: #PCDATA | Inline',
16281 'accesskey' =>
'Character',
16285 $label->excludes = array(
'label' =>
true);
16290 'Optional: #PCDATA | Inline',
16293 'accesskey' =>
'Character',
16300 'Required: option',
16303 'disabled' =>
'Bool#disabled',
16304 'label*' =>
'Text',
16348 $a->formatting =
true;
16349 $a->excludes = array(
'a' =>
true);
16382 if (
$config->get(
'HTML.SafeIframe')) {
16383 $this->safe =
true;
16391 'src' =>
'URI#embedded',
16392 'width' =>
'Length',
16393 'height' =>
'Length',
16395 'scrolling' =>
'Enum#yes,no,auto',
16396 'frameborder' =>
'Enum#0,1',
16397 'longdesc' =>
'URI',
16398 'marginheight' =>
'Pixels',
16399 'marginwidth' =>
'Pixels',
16427 $max =
$config->get(
'HTML.MaxImgLength');
16437 'height' =>
'Pixels#' . $max,
16438 'width' =>
'Pixels#' . $max,
16439 'longdesc' =>
'URI',
16443 if ($max ===
null ||
$config->get(
'HTML.Trusted')) {
16444 $img->attr[
'height'] =
16445 $img->attr[
'width'] =
'Length';
16449 $img->attr_transform_pre[] =
16450 $img->attr_transform_post[] =
16493 'color' =>
'Color',
16499 $this->
addElement(
'center',
'Block',
'Flow',
'Common');
16506 'compact' =>
'Bool#compact'
16513 array(
'Core',
'I18N'),
16515 'color' =>
'Color',
16526 'compact' =>
'Bool#compact'
16530 $s = $this->
addElement(
's',
'Inline',
'Inline',
'Common');
16531 $s->formatting =
true;
16533 $strike = $this->
addElement(
'strike',
'Inline',
'Inline',
'Common');
16534 $strike->formatting =
true;
16536 $u = $this->
addElement(
'u',
'Inline',
'Inline',
'Common');
16537 $u->formatting =
true;
16541 $align =
'Enum#left,right,center,justify';
16544 $address->content_model =
'Inline | #PCDATA | p';
16545 $address->content_model_type =
'optional';
16546 $address->child =
false;
16549 $blockquote->content_model =
'Flow | #PCDATA';
16550 $blockquote->content_model_type =
'optional';
16551 $blockquote->child =
false;
16554 $br->attr[
'clear'] =
'Enum#left,all,right,none';
16557 $caption->attr[
'align'] =
'Enum#top,bottom,left,right';
16560 $div->attr[
'align'] = $align;
16563 $dl->attr[
'compact'] =
'Bool#compact';
16565 for ($i = 1; $i <= 6; $i++) {
16567 $h->attr[
'align'] = $align;
16571 $hr->attr[
'align'] = $align;
16572 $hr->attr[
'noshade'] =
'Bool#noshade';
16573 $hr->attr[
'size'] =
'Pixels';
16574 $hr->attr[
'width'] =
'Length';
16577 $img->attr[
'align'] =
'IAlign';
16578 $img->attr[
'border'] =
'Pixels';
16579 $img->attr[
'hspace'] =
'Pixels';
16580 $img->attr[
'vspace'] =
'Pixels';
16586 $li->attr[
'type'] =
'Enum#s:1,i,I,a,A,disc,square,circle';
16589 $ol->attr[
'compact'] =
'Bool#compact';
16591 $ol->attr[
'type'] =
'Enum#s:1,i,I,a,A';
16594 $p->attr[
'align'] = $align;
16597 $pre->attr[
'width'] =
'Number';
16602 $table->attr[
'align'] =
'Enum#left,center,right';
16603 $table->attr[
'bgcolor'] =
'Color';
16606 $tr->attr[
'bgcolor'] =
'Color';
16609 $th->attr[
'bgcolor'] =
'Color';
16610 $th->attr[
'height'] =
'Length';
16611 $th->attr[
'nowrap'] =
'Bool#nowrap';
16612 $th->attr[
'width'] =
'Length';
16615 $td->attr[
'bgcolor'] =
'Color';
16616 $td->attr[
'height'] =
'Length';
16617 $td->attr[
'nowrap'] =
'Bool#nowrap';
16618 $td->attr[
'width'] =
'Length';
16621 $ul->attr[
'compact'] =
'Bool#compact';
16622 $ul->attr[
'type'] =
'Enum#square,disc,circle';
16630 $form->content_model =
'Flow | #PCDATA';
16631 $form->content_model_type =
'optional';
16632 $form->attr[
'target'] =
'FrameTarget';
16635 $input->attr[
'align'] =
'IAlign';
16638 $legend->attr[
'align'] =
'LAlign';
16685 $this->
addElement(
'dl',
'List',
'Required: dt | dd',
'Common');
16687 $this->
addElement(
'li',
false,
'Flow',
'Common');
16689 $this->
addElement(
'dd',
false,
'Flow',
'Common');
16690 $this->
addElement(
'dt',
false,
'Inline',
'Common');
16710 $elements = array(
'a',
'applet',
'form',
'frame',
'iframe',
'img',
'map');
16713 $element->attr[
'name'] =
'CDATA';
16714 if (!
$config->get(
'HTML.Attr.Name.UseCDATA')) {
16763 'lang' =>
'LanguageCode',
16797 'Optional: #PCDATA | Flow | param',
16800 'archive' =>
'URI',
16801 'classid' =>
'URI',
16802 'codebase' =>
'URI',
16803 'codetype' =>
'Text',
16805 'declare' =>
'Bool#declare',
16806 'height' =>
'Length',
16808 'standby' =>
'Text',
16809 'tabindex' =>
'Number',
16810 'type' =>
'ContentType',
16811 'width' =>
'Length'
16825 'valuetype' =>
'Enum#data,ref,object'
16858 $this->
addElement(
'hr',
'Block',
'Empty',
'Common');
16859 $this->
addElement(
'sub',
'Inline',
'Inline',
'Common');
16860 $this->
addElement(
'sup',
'Inline',
'Inline',
'Common');
16861 $b = $this->
addElement(
'b',
'Inline',
'Inline',
'Common');
16862 $b->formatting =
true;
16863 $big = $this->
addElement(
'big',
'Inline',
'Inline',
'Common');
16864 $big->formatting =
true;
16865 $i = $this->
addElement(
'i',
'Inline',
'Inline',
'Common');
16866 $i->formatting =
true;
16867 $small = $this->
addElement(
'small',
'Inline',
'Inline',
'Common');
16868 $small->formatting =
true;
16869 $tt = $this->
addElement(
'tt',
'Inline',
'Inline',
'Common');
16870 $tt->formatting =
true;
16900 'direction' =>
'Enum#left,right,up,down',
16901 'behavior' =>
'Enum#alternate',
16902 'width' =>
'Length',
16903 'height' =>
'Length',
16904 'scrolldelay' =>
'Number',
16905 'scrollamount' =>
'Number',
16906 'loop' =>
'Number',
16907 'bgcolor' =>
'Color',
16908 'hspace' =>
'Pixels',
16909 'vspace' =>
'Pixels',
16939 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
16942 $this->
addElement(
'rbc',
false,
'Required: rb',
'Common');
16943 $this->
addElement(
'rtc',
false,
'Required: rt',
'Common');
16944 $rb = $this->
addElement(
'rb',
false,
'Inline',
'Common');
16945 $rb->excludes = array(
'ruby' =>
true);
16946 $rt = $this->
addElement(
'rt',
false,
'Inline',
'Common', array(
'rbspan' =>
'Number'));
16947 $rt->excludes = array(
'ruby' =>
true);
16948 $this->
addElement(
'rp',
false,
'Optional: #PCDATA',
'Common');
16971 $max =
$config->get(
'HTML.MaxImgLength');
16978 'src*' =>
'URI#embedded',
16979 'type' =>
'Enum#application/x-shockwave-flash',
16980 'width' =>
'Pixels#' . $max,
16981 'height' =>
'Pixels#' . $max,
16982 'allowscriptaccess' =>
'Enum#never',
16983 'allownetworking' =>
'Enum#internal',
16984 'flashvars' =>
'Text',
16985 'wmode' =>
'Enum#window,transparent,opaque',
17018 $max =
$config->get(
'HTML.MaxImgLength');
17022 'Optional: param | Flow | #PCDATA',
17027 'type' =>
'Enum#application/x-shockwave-flash',
17028 'width' =>
'Pixels#' . $max,
17029 'height' =>
'Pixels#' . $max,
17030 'data' =>
'URI#embedded',
17033 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
17052 $this->info_injector[] =
'SafeObject';
17079 $allowed =
$config->get(
'HTML.SafeScripting');
17088 'type' =>
'Enum#text/javascript',
17092 $script->attr_transform_pre[] =
17129 public $content_sets = array(
'Block' =>
'script | noscript',
'Inline' =>
'script | noscript');
17153 $this->info[
'noscript']->attr = array(0 => array(
'Common'));
17154 $this->info[
'noscript']->content_model =
'Heading | List | Block';
17155 $this->info[
'noscript']->content_model_type =
'required';
17158 $this->info[
'script']->attr = array(
17163 $this->info[
'script']->content_model =
'#PCDATA';
17164 $this->info[
'script']->content_model_type =
'optional';
17165 $this->info[
'script']->attr_transform_pre[] =
17166 $this->info[
'script']->attr_transform_post[] =
17192 'Style' => array(
'style' =>
false),
17193 'Core' => array(0 => array(
'Style'))
17224 $this->
addElement(
'caption',
false,
'Inline',
'Common');
17232 'border' =>
'Pixels',
17233 'cellpadding' =>
'Length',
17234 'cellspacing' =>
'Length',
17235 'frame' =>
'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
17236 'rules' =>
'Enum#none,groups,rows,cols,all',
17237 'summary' =>
'Text',
17238 'width' =>
'Length'
17243 $cell_align = array(
17244 'align' =>
'Enum#left,center,right,justify,char',
17245 'charoff' =>
'Length',
17246 'valign' =>
'Enum#top,middle,bottom,baseline',
17249 $cell_t = array_merge(
17252 'colspan' =>
'Number',
17253 'rowspan' =>
'Number',
17256 'scope' =>
'Enum#row,col,rowgroup,colgroup',
17260 $this->
addElement(
'td',
false,
'Flow',
'Common', $cell_t);
17261 $this->
addElement(
'th',
false,
'Flow',
'Common', $cell_t);
17263 $this->
addElement(
'tr',
false,
'Required: td | th',
'Common', $cell_align);
17265 $cell_col = array_merge(
17267 'span' =>
'Number',
17268 'width' =>
'MultiLength',
17272 $this->
addElement(
'col',
false,
'Empty',
'Common', $cell_col);
17273 $this->
addElement(
'colgroup',
false,
'Optional: col',
'Common', $cell_col);
17275 $this->
addElement(
'tbody',
false,
'Required: tr',
'Common', $cell_align);
17276 $this->
addElement(
'thead',
false,
'Required: tr',
'Common', $cell_align);
17277 $this->
addElement(
'tfoot',
false,
'Required: tr',
'Common', $cell_align);
17406 'Flow' =>
'Heading | Block | Inline'
17415 $this->
addElement(
'abbr',
'Inline',
'Inline',
'Common');
17416 $this->
addElement(
'acronym',
'Inline',
'Inline',
'Common');
17417 $this->
addElement(
'cite',
'Inline',
'Inline',
'Common');
17418 $this->
addElement(
'dfn',
'Inline',
'Inline',
'Common');
17419 $this->
addElement(
'kbd',
'Inline',
'Inline',
'Common');
17420 $this->
addElement(
'q',
'Inline',
'Inline',
'Common', array(
'cite' =>
'URI'));
17421 $this->
addElement(
'samp',
'Inline',
'Inline',
'Common');
17422 $this->
addElement(
'var',
'Inline',
'Inline',
'Common');
17424 $em = $this->
addElement(
'em',
'Inline',
'Inline',
'Common');
17425 $em->formatting =
true;
17427 $strong = $this->
addElement(
'strong',
'Inline',
'Inline',
'Common');
17428 $strong->formatting =
true;
17430 $code = $this->
addElement(
'code',
'Inline',
'Inline',
'Common');
17431 $code->formatting =
true;
17434 $this->
addElement(
'span',
'Inline',
'Inline',
'Common');
17435 $this->
addElement(
'br',
'Inline',
'Empty',
'Core');
17438 $this->
addElement(
'address',
'Block',
'Inline',
'Common');
17439 $this->
addElement(
'blockquote',
'Block',
'Optional: Heading | Block | List',
'Common', array(
'cite' =>
'URI'));
17440 $pre = $this->
addElement(
'pre',
'Block',
'Inline',
'Common');
17450 $this->
addElement(
'h1',
'Heading',
'Inline',
'Common');
17451 $this->
addElement(
'h2',
'Heading',
'Inline',
'Common');
17452 $this->
addElement(
'h3',
'Heading',
'Inline',
'Common');
17453 $this->
addElement(
'h4',
'Heading',
'Inline',
'Common');
17454 $this->
addElement(
'h5',
'Heading',
'Inline',
'Common');
17455 $this->
addElement(
'h6',
'Heading',
'Inline',
'Common');
17458 $p = $this->
addElement(
'p',
'Block',
'Inline',
'Common');
17459 $p->autoclose = array_flip(
17460 array(
"address",
"blockquote",
"center",
"dir",
"div",
"dl",
"fieldset",
"ol",
"p",
"ul")
17463 $this->
addElement(
'div',
'Block',
'Flow',
'Common');
17483 public $levels = array(0 =>
'none',
'light',
'medium',
'heavy');
17499 'light' => array(),
17500 'medium' => array(),
17518 $level =
$config->get(
'HTML.TidyLevel');
17522 $add_fixes =
$config->get(
'HTML.TidyAdd');
17523 $remove_fixes =
$config->get(
'HTML.TidyRemove');
17525 foreach ($fixes
as $name => $fix) {
17527 if (isset($remove_fixes[
$name]) ||
17528 (!isset($add_fixes[
$name]) && !isset($fixes_lookup[
$name]))) {
17529 unset($fixes[
$name]);
17545 if ($level == $this->levels[0]) {
17548 $activated_levels = array();
17549 for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
17550 $activated_levels[] = $this->levels[$i];
17551 if ($this->levels[$i] == $level) {
17557 'Tidy level ' . htmlspecialchars($level) .
' not recognized',
17563 foreach ($activated_levels
as $level) {
17564 foreach ($this->fixesForLevel[$level]
as $fix) {
17579 if (!isset($this->defaultLevel)) {
17582 if (!isset($this->fixesForLevel[$this->defaultLevel])) {
17584 'Default level ' . $this->defaultLevel .
' does not exist',
17599 foreach ($fixes
as $name => $fix) {
17603 case 'attr_transform_pre':
17604 case 'attr_transform_post':
17605 $attr = $params[
'attr'];
17606 if (isset($params[
'element'])) {
17607 $element = $params[
'element'];
17608 if (
empty($this->info[$element])) {
17611 $e = $this->info[$element];
17614 $type =
"info_$type";
17622 case 'tag_transform':
17623 $this->info_tag_transform[$params[
'element']] = $fix;
17626 case 'content_model_type':
17627 $element = $params[
'element'];
17628 if (
empty($this->info[$element])) {
17631 $e = $this->info[$element];
17636 trigger_error(
"Fix type $type not supported", E_USER_ERROR);
17653 $property = $attr =
null;
17654 if (strpos(
$name,
'#') !==
false) {
17655 list(
$name, $property) = explode(
'#',
$name);
17657 if (strpos(
$name,
'@') !==
false) {
17663 if (
$name !==
'') {
17664 $params[
'element'] =
$name;
17666 if (!is_null($attr)) {
17667 $params[
'attr'] = $attr;
17671 if (!is_null($attr)) {
17672 if (is_null($property)) {
17675 $type =
'attr_transform_' . $property;
17676 return array($type, $params);
17680 if (is_null($property)) {
17681 return array(
'tag_transform', $params);
17684 return array($property, $params);
17714 'xml:lang' =>
'LanguageCode',
17814 $r[
'caption@align'] =
17822 'left' =>
'text-align:left;',
17823 'right' =>
'text-align:right;',
17824 'top' =>
'caption-side:top;',
17825 'bottom' =>
'caption-side:bottom;'
17834 'left' =>
'float:left;',
17835 'right' =>
'float:right;',
17836 'top' =>
'vertical-align:top;',
17837 'middle' =>
'vertical-align:middle;',
17838 'bottom' =>
'vertical-align:baseline;',
17843 $r[
'table@align'] =
17847 'left' =>
'float:left;',
17848 'center' =>
'margin-left:auto;margin-right:auto;',
17849 'right' =>
'float:right;'
17862 'left' =>
'margin-left:0;margin-right:auto;text-align:left;',
17863 'center' =>
'margin-left:auto;margin-right:auto;text-align:center;',
17864 'right' =>
'margin-left:auto;margin-right:0;text-align:right;'
17870 $align_lookup = array();
17871 $align_values = array(
'left',
'right',
'center',
'justify');
17872 foreach ($align_values
as $v) {
17873 $align_lookup[$v] =
"text-align:$v;";
17887 $r[
'table@bgcolor'] =
17900 'left' =>
'clear:left;',
17901 'right' =>
'clear:right;',
17902 'all' =>
'clear:both;',
17903 'none' =>
'clear:none;',
17921 'color:#808080;background-color:#808080;border:0;'
17929 'white-space:nowrap;'
17938 'disc' =>
'list-style-type:disc;',
17939 'square' =>
'list-style-type:square;',
17940 'circle' =>
'list-style-type:circle;'
17943 '1' =>
'list-style-type:decimal;',
17944 'i' =>
'list-style-type:lower-roman;',
17945 'I' =>
'list-style-type:upper-roman;',
17946 'a' =>
'list-style-type:lower-alpha;',
17947 'A' =>
'list-style-type:upper-alpha;'
17949 $li_types = $ul_types + $ol_types;
17989 $r = parent::makeFixes();
17990 $r[
'blockquote#content_model_type'] =
'strictblockquote';
18005 if (
$def->content_model_type !=
'strictblockquote') {
18006 return parent::getChildDef(
$def);
18081 private function _pStart()
18084 $par->armor[
'MakeWellFormed_TagClosedError'] =
true;
18093 $text = $token->data;
18096 if (
empty($this->currentNesting) || strpos($text,
"\n\n") !==
false) {
18102 $i = $nesting =
null;
18108 if (!$token->is_whitespace || $this->_isInline($current)) {
18117 $token = array($this->_pStart());
18118 $this->_splitText($text, $token);
18130 if ($this->_pLookAhead()) {
18137 $token = array($this->_pStart(), $token);
18147 } elseif (!
empty($this->currentNesting) &&
18148 $this->currentNesting[count($this->currentNesting) - 1]->
name ==
'p') {
18155 $this->_splitText($text, $token);
18174 if (!
empty($this->currentNesting)) {
18175 if ($this->_isInline($token)) {
18186 substr($prev->data, -2) ===
"\n\n"
18191 $token = array($this->_pStart(), $token);
18204 if ($this->_pLookAhead()) {
18207 $token = array($this->_pStart(), $token);
18221 if ($this->_isInline($token)) {
18226 $token = array($this->_pStart(), $token);
18239 if (!is_array($token)) {
18240 $token = array($token);
18269 private function _splitText($data, &$result)
18271 $raw_paragraphs = explode(
"\n\n", $data);
18272 $paragraphs = array();
18273 $needs_start =
false;
18274 $needs_end =
false;
18276 $c = count($raw_paragraphs);
18283 for ($i = 0; $i < $c; $i++) {
18284 $par = $raw_paragraphs[$i];
18285 if (trim($par) !==
'') {
18286 $paragraphs[] = $par;
18290 if (
empty($result)) {
18302 $needs_start =
true;
18309 } elseif ($i + 1 == $c) {
18319 if (
empty($paragraphs)) {
18324 if ($needs_start) {
18325 $result[] = $this->_pStart();
18329 foreach ($paragraphs
as $par) {
18333 $result[] = $this->_pStart();
18339 array_pop($result);
18344 array_pop($result);
18345 array_pop($result);
18355 private function _isInline($token)
18357 return isset($this->htmlDefinition->info[
'p']->child->elements[$token->name]);
18365 private function _pLookAhead()
18375 $result = $this->_checkNeedsP($current);
18376 if ($result !==
null) {
18390 private function _checkNeedsP($current)
18393 if (!$this->_isInline($current)) {
18400 if (strpos($current->data,
"\n\n") !==
false) {
18444 if (isset($token->start->attr[
'href'])) {
18445 $url = $token->start->attr[
'href'];
18446 unset($token->start->attr[
'href']);
18482 if (strpos($token->data,
'://') ===
false) {
18494 $bits = preg_split(
18495 '/\\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',
18496 $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
18504 for ($i = 0, $c = count($bits), $l =
false; $i < $c; $i++, $l = !$l) {
18506 if ($bits[$i] ===
'') {
18551 $this->docURL =
$config->get(
'AutoFormat.PurifierLinkify.DocURL');
18552 return parent::prepare(
$config, $context);
18563 if (strpos($token->data,
'%') ===
false) {
18567 $bits = preg_split(
'#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
18573 for ($i = 0, $c = count($bits), $l =
false; $i < $c; $i++, $l = !$l) {
18575 if ($bits[$i] ===
'') {
18582 array(
'href' => str_replace(
'%s', $bits[$i], $this->docURL))
18610 private $attrValidator;
18615 private $removeNbsp;
18620 private $removeNbspExceptions;
18635 parent::prepare($config, $context);
18636 $this->config = $config;
18638 $this->removeNbsp = $config->get(
'AutoFormat.RemoveEmpty.RemoveNbsp');
18639 $this->removeNbspExceptions = $config->get(
'AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
18640 $this->exclude = $config->get(
'AutoFormat.RemoveEmpty.Predicate');
18641 foreach ($this->exclude
as $key => $attrs) {
18642 if (!is_array($attrs)) {
18644 $this->exclude[$key] = explode(
';', $attrs);
18660 for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
18661 $next = $this->inputZipper->back[$i];
18663 if ($next->is_whitespace) {
18666 if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
18667 $plain = str_replace(
"\xC2\xA0",
"", $next->data);
18668 $isWsOrNbsp = $plain ===
'' || ctype_space($plain);
18677 $this->attrValidator->validateToken($token, $this->config, $this->
context);
18678 $token->armor[
'ValidateAttributes'] =
true;
18679 if (isset($this->exclude[$token->name])) {
18681 foreach ($this->exclude[$token->name]
as $elem) {
18682 if (!isset($token->attr[$elem])) $r =
false;
18686 if (isset($token->attr[
'id']) || isset($token->attr[
'name'])) {
18689 $token = $deleted + 1;
18690 for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
18691 $prev = $this->inputZipper->front[$b];
18716 public $name =
'RemoveSpansWithoutAttributes';
18726 private $attrValidator;
18742 $this->config = $config;
18744 return parent::prepare($config, $context);
18759 $this->attrValidator->validateToken($token, $this->config, $this->
context);
18760 $token->armor[
'ValidateAttributes'] =
true;
18762 if (!
empty($token->attr)) {
18772 $current->markForDeletion =
true;
18783 if ($token->markForDeletion) {
18824 'allowScriptAccess' =>
'never',
18825 'allowNetworking' =>
'internal',
18835 'flashvars' =>
true,
18837 'allowfullscreen' =>
true,
18847 parent::prepare(
$config, $context);
18855 if ($token->name ==
'object') {
18856 $this->objectStack[] = $token;
18857 $this->paramStack[] = array();
18858 $new = array($token);
18859 foreach ($this->addParam
as $name => $value) {
18863 } elseif ($token->name ==
'param') {
18864 $nest = count($this->currentNesting) - 1;
18865 if ($nest >= 0 && $this->currentNesting[$nest]->
name ===
'object') {
18866 $i = count($this->objectStack) - 1;
18867 if (!isset($token->attr[
'name'])) {
18871 $n = $token->attr[
'name'];
18875 if (!isset($this->objectStack[$i]->attr[
'data']) &&
18876 ($token->attr[
'name'] ==
'movie' || $token->attr[
'name'] ==
'src')
18878 $this->objectStack[$i]->attr[
'data'] = $token->attr[
'value'];
18882 if (!isset($this->paramStack[$i][$n]) &&
18883 isset($this->addParam[$n]) &&
18884 $token->attr[
'name'] === $this->addParam[$n]) {
18886 $this->paramStack[$i][$n] =
true;
18887 } elseif (isset($this->allowedParam[strtolower($n)])) {
18907 if ($token->name ==
'object') {
18908 array_pop($this->objectStack);
18909 array_pop($this->paramStack);
18953 parent::__construct();
18969 if (
$config->get(
'Core.AggressivelyFixLt')) {
18970 $char =
'[^a-z!\/]';
18971 $comment =
"/<!--(.*?)(-->|\z)/is";
18972 $html = preg_replace_callback($comment, array($this,
'callbackArmorCommentEntities'),
$html);
18975 $html = preg_replace(
"/<($char)/i",
'<\\1',
$html);
18976 }
while (
$html !== $old);
18977 $html = preg_replace_callback($comment, array($this,
'callbackUndoCommentSubst'),
$html);
18983 $doc =
new DOMDocument();
18984 $doc->encoding =
'UTF-8';
18987 if (
$config->get(
'Core.AllowParseManyTags') && defined(
'LIBXML_PARSEHUGE')) {
18988 $options |= LIBXML_PARSEHUGE;
18991 set_error_handler(array($this,
'muteErrorHandler'));
18994 $doc->loadHTML(
$html, $options);
18996 $doc->loadHTML(
$html);
18998 restore_error_handler();
19000 $body = $doc->getElementsByTagName(
'html')->item(0)->
19001 getElementsByTagName(
'body')->item(0);
19003 $div = $body->getElementsByTagName(
'div')->item(0);
19010 if ($div->nextSibling) {
19011 $body->removeChild($div);
19028 $closingNodes = array();
19030 while (!$nodes[$level]->isEmpty()) {
19031 $node = $nodes[$level]->shift();
19032 $collect = $level > 0 ?
true :
false;
19034 if ($needEndingTag) {
19035 $closingNodes[$level][] = $node;
19037 if ($node->childNodes && $node->childNodes->length) {
19040 foreach ($node->childNodes
as $childNode) {
19041 $nodes[$level]->push($childNode);
19046 if ($level && isset($closingNodes[$level])) {
19047 while ($node = array_pop($closingNodes[$level])) {
19051 }
while ($level > 0);
19061 if (isset($node->tagName)) {
19062 return $node->tagName;
19063 }
else if (isset($node->nodeName)) {
19064 return $node->nodeName;
19065 }
else if (isset($node->localName)) {
19066 return $node->localName;
19078 if (isset($node->data)) {
19079 return $node->data;
19080 }
else if (isset($node->nodeValue)) {
19081 return $node->nodeValue;
19082 }
else if (isset($node->textContent)) {
19083 return $node->textContent;
19103 if ($node->nodeType === XML_TEXT_NODE) {
19104 $data = $this->
getData($node);
19105 if ($data !==
null) {
19106 $tokens[] = $this->factory->createText($data);
19109 } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
19111 $last = end($tokens);
19112 $data = $node->data;
19115 $new_data = trim($data);
19116 if (substr($new_data, 0, 4) ===
'<!--') {
19117 $data = substr($new_data, 4);
19118 if (substr($data, -3) ===
'-->') {
19119 $data = substr($data, 0, -3);
19127 } elseif ($node->nodeType === XML_COMMENT_NODE) {
19131 $tokens[] = $this->factory->createComment($node->data);
19133 } elseif ($node->nodeType !== XML_ELEMENT_NODE) {
19139 if (
empty($tag_name)) {
19140 return (
bool) $node->childNodes->length;
19143 if (!$node->childNodes->length) {
19145 $tokens[] = $this->factory->createEmpty($tag_name, $attr);
19150 $tokens[] = $this->factory->createStart($tag_name, $attr);
19163 $tokens[] = $this->factory->createEnd($tag_name);
19177 if ($node_map->length === 0) {
19181 foreach ($node_map
as $attr) {
19182 $array[$attr->name] = $attr->value;
19204 return '<!--' . strtr($matches[1], array(
'&' =>
'&',
'<' =>
'<')) . $matches[2];
19215 return '<!--' . str_replace(
'&',
'&', $matches[1]) . $matches[2];
19231 $ret .=
'<!DOCTYPE html ';
19232 if (!
empty(
$def->doctype->dtdPublic)) {
19233 $ret .=
'PUBLIC "' .
$def->doctype->dtdPublic .
'" ';
19235 if (!
empty(
$def->doctype->dtdSystem)) {
19236 $ret .=
'"' .
$def->doctype->dtdSystem .
'" ';
19241 $ret .=
'<html><head>';
19242 $ret .=
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
19244 $ret .=
'</head><body>';
19245 if ($use_div)
$ret .=
'<div>';
19247 if ($use_div)
$ret .=
'</div>';
19248 $ret .=
'</body></html>';
19287 return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT,
'UTF-8') . $matches[3];
19301 if (
$config->get(
'HTML.Trusted')) {
19302 $html = preg_replace_callback(
19303 '#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
19304 array($this,
'scriptCallback'),
19312 $inside_tag =
false;
19316 $maintain_line_numbers =
$config->get(
'Core.MaintainLineNumbers');
19318 if ($maintain_line_numbers ===
null) {
19321 $maintain_line_numbers =
$config->get(
'Core.CollectErrors');
19324 if ($maintain_line_numbers) {
19327 $length = strlen(
$html);
19329 $current_line =
false;
19330 $current_col =
false;
19333 $context->register(
'CurrentLine', $current_line);
19334 $context->register(
'CurrentCol', $current_col);
19338 $synchronize_interval =
$config->get(
'Core.DirectLexLineNumberSyncInterval');
19341 if (
$config->get(
'Core.CollectErrors')) {
19342 $e =& $context->get(
'ErrorCollector');
19353 if ($maintain_line_numbers) {
19355 $rcursor = $cursor - (int)$inside_tag;
19361 $nl_pos = strrpos(
$html, $nl, $rcursor - $length);
19362 $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
19365 if ($synchronize_interval &&
19367 $loops % $synchronize_interval === 0) {
19372 $position_next_lt = strpos(
$html,
'<', $cursor);
19373 $position_next_gt = strpos(
$html,
'>', $cursor);
19377 if ($position_next_lt === $cursor) {
19378 $inside_tag =
true;
19382 if (!$inside_tag && $position_next_lt !==
false) {
19390 $position_next_lt - $cursor
19394 if ($maintain_line_numbers) {
19395 $token->rawPosition($current_line, $current_col);
19396 $current_line += $this->
substrCount(
$html, $nl, $cursor, $position_next_lt - $cursor);
19399 $cursor = $position_next_lt + 1;
19400 $inside_tag =
true;
19402 } elseif (!$inside_tag) {
19405 if ($cursor === strlen(
$html)) {
19418 if ($maintain_line_numbers) {
19419 $token->rawPosition($current_line, $current_col);
19423 } elseif ($inside_tag && $position_next_gt !==
false) {
19426 $strlen_segment = $position_next_gt - $cursor;
19428 if ($strlen_segment < 1) {
19435 $segment = substr(
$html, $cursor, $strlen_segment);
19437 if ($segment ===
false) {
19444 if (substr($segment, 0, 3) ===
'!--') {
19446 $position_comment_end = strpos(
$html,
'-->', $cursor);
19447 if ($position_comment_end ===
false) {
19452 $e->send(E_WARNING,
'Lexer: Unclosed comment');
19454 $position_comment_end = strlen(
$html);
19459 $strlen_segment = $position_comment_end - $cursor;
19460 $segment = substr(
$html, $cursor, $strlen_segment);
19466 $strlen_segment - 3
19469 if ($maintain_line_numbers) {
19470 $token->rawPosition($current_line, $current_col);
19471 $current_line += $this->
substrCount(
$html, $nl, $cursor, $strlen_segment);
19474 $cursor = $end ? $position_comment_end : $position_comment_end + 3;
19475 $inside_tag =
false;
19480 $is_end_tag = (strpos($segment,
'/') === 0);
19482 $type = substr($segment, 1);
19484 if ($maintain_line_numbers) {
19485 $token->rawPosition($current_line, $current_col);
19486 $current_line += $this->
substrCount(
$html, $nl, $cursor, $position_next_gt - $cursor);
19489 $inside_tag =
false;
19490 $cursor = $position_next_gt + 1;
19497 if (!ctype_alpha($segment[0])) {
19500 $e->send(E_NOTICE,
'Lexer: Unescaped lt');
19503 if ($maintain_line_numbers) {
19504 $token->rawPosition($current_line, $current_col);
19505 $current_line += $this->
substrCount(
$html, $nl, $cursor, $position_next_gt - $cursor);
19508 $inside_tag =
false;
19516 $is_self_closing = (strrpos($segment,
'/') === $strlen_segment - 1);
19517 if ($is_self_closing) {
19519 $segment = substr($segment, 0, $strlen_segment);
19523 $position_first_space = strcspn($segment, $this->_whitespace);
19525 if ($position_first_space >= $strlen_segment) {
19526 if ($is_self_closing) {
19531 if ($maintain_line_numbers) {
19532 $token->rawPosition($current_line, $current_col);
19533 $current_line += $this->
substrCount(
$html, $nl, $cursor, $position_next_gt - $cursor);
19536 $inside_tag =
false;
19537 $cursor = $position_next_gt + 1;
19542 $type = substr($segment, 0, $position_first_space);
19543 $attribute_string =
19547 $position_first_space
19550 if ($attribute_string) {
19560 if ($is_self_closing) {
19565 if ($maintain_line_numbers) {
19566 $token->rawPosition($current_line, $current_col);
19567 $current_line += $this->
substrCount(
$html, $nl, $cursor, $position_next_gt - $cursor);
19570 $cursor = $position_next_gt + 1;
19571 $inside_tag =
false;
19576 $e->send(E_WARNING,
'Lexer: Missing gt');
19585 if ($maintain_line_numbers) {
19586 $token->rawPosition($current_line, $current_col);
19595 $context->destroy(
'CurrentLine');
19596 $context->destroy(
'CurrentCol');
19610 static $oldVersion;
19611 if ($oldVersion ===
null) {
19612 $oldVersion = version_compare(PHP_VERSION,
'5.1',
'<');
19615 $haystack = substr($haystack, $offset, $length);
19616 return substr_count($haystack, $needle);
19618 return substr_count($haystack, $needle, $offset, $length);
19632 $string = (string)$string;
19634 if ($string ==
'') {
19639 if (
$config->get(
'Core.CollectErrors')) {
19640 $e =& $context->get(
'ErrorCollector');
19645 $num_equal = substr_count($string,
'=');
19646 $has_space = strpos($string,
' ');
19647 if ($num_equal === 0 && !$has_space) {
19649 return array($string => $string);
19650 } elseif ($num_equal === 1 && !$has_space) {
19652 list($key, $quoted_value) = explode(
'=', $string);
19653 $quoted_value = trim($quoted_value);
19656 $e->send(E_ERROR,
'Lexer: Missing attribute key');
19660 if (!$quoted_value) {
19661 return array($key =>
'');
19663 $first_char = @$quoted_value[0];
19664 $last_char = @$quoted_value[strlen($quoted_value) - 1];
19666 $same_quote = ($first_char == $last_char);
19667 $open_quote = ($first_char ==
'"' || $first_char ==
"'");
19669 if ($same_quote && $open_quote) {
19671 $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
19676 $e->send(E_ERROR,
'Lexer: Missing end quote');
19678 $value = substr($quoted_value, 1);
19680 $value = $quoted_value;
19683 if ($value ===
false) {
19692 $size = strlen($string);
19699 while ($cursor < $size) {
19700 if ($old_cursor >= $cursor) {
19701 throw new Exception(
"Infinite loop detected");
19703 $old_cursor = $cursor;
19705 $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
19708 $key_begin = $cursor;
19711 $cursor += strcspn($string, $this->_whitespace .
'=', $cursor);
19713 $key_end = $cursor;
19715 $key = substr($string, $key_begin, $key_end - $key_begin);
19719 $e->send(E_ERROR,
'Lexer: Missing attribute key');
19721 $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1);
19726 $cursor += strspn($string, $this->_whitespace, $cursor);
19728 if ($cursor >= $size) {
19729 $array[$key] = $key;
19735 $first_char = @$string[$cursor];
19737 if ($first_char ==
'=') {
19741 $cursor += strspn($string, $this->_whitespace, $cursor);
19743 if ($cursor ===
false) {
19750 $char = @$string[$cursor];
19752 if ($char ==
'"' || $char ==
"'") {
19755 $value_begin = $cursor;
19756 $cursor = strpos($string, $char, $cursor);
19757 $value_end = $cursor;
19760 $value_begin = $cursor;
19761 $cursor += strcspn($string, $this->_whitespace, $cursor);
19762 $value_end = $cursor;
19766 if ($cursor ===
false) {
19768 $value_end = $cursor;
19771 $value = substr($string, $value_begin, $value_end - $value_begin);
19772 if ($value ===
false) {
19780 $array[$key] = $key;
19784 $e->send(E_ERROR,
'Lexer: Missing attribute key');
19822 $this->data =
$data;
19823 $this->line =
$line;
19872 $this->attr =
$attr;
19873 $this->line =
$line;
19880 if ($this->
empty) {
19886 return array($start, $end);
19934 $this->data =
$data;
19936 $this->line =
$line;
19969 foreach ($this->strategies
as $strategy) {
19970 $tokens = $strategy->execute($tokens,
$config, $context);
20047 $definition =
$config->getHTMLDefinition();
20049 $excludes_enabled = !
$config->get(
'Core.DisableExcludes');
20055 $is_inline = $definition->info_parent_def->descendants_are_inline;
20056 $context->register(
'IsInline', $is_inline);
20059 $e =& $context->get(
'ErrorCollector',
true);
20068 $exclude_stack = array($definition->info_parent_def->excludes);
20074 list($token, $d) = $node->toTokenPair();
20075 $context->register(
'CurrentNode', $node);
20076 $context->register(
'CurrentToken', $token);
20098 $parent_def = $definition->info_parent_def;
20101 $parent_def->descendants_are_inline,
20102 $parent_def->excludes,
20106 while (!
empty($stack)) {
20107 list($node, $is_inline, $excludes, $ix) = array_pop($stack);
20110 $def =
empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
20111 while (isset($node->children[$ix])) {
20112 $child = $node->children[$ix++];
20115 $stack[] = array($node, $is_inline, $excludes, $ix);
20116 $stack[] = array($child,
20119 $is_inline ||
$def->descendants_are_inline,
20121 : array_merge($excludes,
$def->excludes),
20127 list($token, $d) = $node->toTokenPair();
20129 if ($excludes_enabled && isset($excludes[$node->name])) {
20130 $node->dead =
true;
20131 if ($e) $e->send(E_ERROR,
'Strategy_FixNesting: Node excluded');
20136 $children = array();
20137 foreach ($node->children
as $child) {
20138 if (!$child->dead) $children[] = $child;
20140 $result =
$def->child->validateChildren($children,
$config, $context);
20141 if ($result ===
true) {
20143 $node->children = $children;
20144 } elseif ($result ===
false) {
20145 $node->dead =
true;
20146 if ($e) $e->send(E_ERROR,
'Strategy_FixNesting: Node removed');
20148 $node->children = $result;
20152 $e->send(E_ERROR,
'Strategy_FixNesting: Node contents removed');
20153 }
else if ($result != $children) {
20154 $e->send(E_WARNING,
'Strategy_FixNesting: Node reorganized');
20165 $context->destroy(
'IsInline');
20166 $context->destroy(
'CurrentNode');
20167 $context->destroy(
'CurrentToken');
20245 $definition =
$config->getHTMLDefinition();
20249 $escape_invalid_tags =
$config->get(
'Core.EscapeInvalidTags');
20251 $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements(
$config);
20252 $e =
$context->get(
'ErrorCollector',
true);
20258 $reprocess =
false;
20276 $this->injectors = array();
20279 $def_injectors = $definition->info_injector;
20284 if (strpos($injector,
'.') !==
false) {
20287 $injector =
"HTMLPurifier_Injector_$injector";
20291 $this->injectors[] =
new $injector;
20293 foreach ($def_injectors
as $injector) {
20295 $this->injectors[] = $injector;
20297 foreach ($custom_injectors
as $injector) {
20301 if (is_string($injector)) {
20302 $injector =
"HTMLPurifier_Injector_$injector";
20303 $injector =
new $injector;
20305 $this->injectors[] = $injector;
20310 foreach ($this->injectors
as $ix => $injector) {
20315 array_splice($this->injectors, $ix, 1);
20316 trigger_error(
"Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
20339 $rewind_offset = $this->injectors[$i]->getRewindOffset();
20340 if (is_int($rewind_offset)) {
20341 for ($j = 0; $j < $rewind_offset; $j++) {
20346 unset(
$token->skip[$i]);
20349 array_pop($this->stack);
20351 $this->stack[] =
$token->start;
20361 if (
empty($this->stack)) {
20366 $top_nesting = array_pop($this->stack);
20367 $this->stack[] = $top_nesting;
20370 if ($e && !isset($top_nesting->armor[
'MakeWellFormed_TagClosedError'])) {
20371 $e->send(E_NOTICE,
'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);
20388 foreach ($this->injectors
as $i => $injector) {
20389 if (isset(
$token->skip[$i])) {
20393 if (
$token->rewind !==
null &&
$token->rewind !== $i) {
20398 $injector->handleText($r);
20408 if (isset($definition->info[
$token->name])) {
20409 $type = $definition->info[
$token->name]->child->type;
20431 $token = $this->insertBefore(
20432 new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
20444 if (!
empty($this->stack)) {
20459 $parent = array_pop($this->stack);
20460 $this->stack[] = $parent;
20462 $parent_def =
null;
20463 $parent_elements =
null;
20464 $autoclose =
false;
20465 if (isset($definition->info[$parent->name])) {
20466 $parent_def = $definition->info[$parent->name];
20467 $parent_elements = $parent_def->child->getAllowedElements(
$config);
20468 $autoclose = !isset($parent_elements[
$token->name]);
20471 if ($autoclose && $definition->info[
$token->name]->wrap) {
20475 $wrapname = $definition->info[
$token->name]->wrap;
20476 $wrapdef = $definition->info[$wrapname];
20477 $elements = $wrapdef->child->getAllowedElements(
$config);
20478 if (isset($elements[
$token->name]) && isset($parent_elements[$wrapname])) {
20480 $token = $this->insertBefore($newtoken);
20486 $carryover =
false;
20487 if ($autoclose && $parent_def->formatting) {
20494 $autoclose_ok = isset($global_parent_allowed_elements[
$token->name]);
20495 if (!$autoclose_ok) {
20496 foreach ($this->stack
as $ancestor) {
20497 $elements = $definition->info[$ancestor->name]->child->getAllowedElements(
$config);
20498 if (isset($elements[
$token->name])) {
20499 $autoclose_ok =
true;
20502 if ($definition->info[
$token->name]->wrap) {
20503 $wrapname = $definition->info[
$token->name]->wrap;
20504 $wrapdef = $definition->info[$wrapname];
20505 $wrap_elements = $wrapdef->child->getAllowedElements(
$config);
20506 if (isset($wrap_elements[
$token->name]) && isset($elements[$wrapname])) {
20507 $autoclose_ok =
true;
20513 if ($autoclose_ok) {
20516 $new_token->start = $parent;
20518 if ($e && !isset($parent->armor[
'MakeWellFormed_TagClosedError'])) {
20520 $e->send(E_NOTICE,
'Strategy_MakeWellFormed: Tag auto closed', $parent);
20522 $e->send(E_NOTICE,
'Strategy_MakeWellFormed: Tag carryover', $parent);
20526 $element = clone $parent;
20528 $element->armor[
'MakeWellFormed_TagClosedError'] =
true;
20529 $element->carryover =
true;
20532 $token = $this->insertBefore($new_token);
20535 $token = $this->
remove();
20546 foreach ($this->injectors
as $i => $injector) {
20547 if (isset(
$token->skip[$i])) {
20551 if (
$token->rewind !==
null &&
$token->rewind !== $i) {
20555 $injector->handleElement($r);
20563 $this->stack[] =
$token;
20566 'Improper handling of end tag in start code; possible error in MakeWellFormed'
20579 if (
empty($this->stack)) {
20580 if ($escape_invalid_tags) {
20582 $e->send(E_WARNING,
'Strategy_MakeWellFormed: Unnecessary end tag to text');
20587 $e->send(E_WARNING,
'Strategy_MakeWellFormed: Unnecessary end tag removed');
20589 $token = $this->
remove();
20599 $current_parent = array_pop($this->stack);
20600 if ($current_parent->name ==
$token->name) {
20601 $token->start = $current_parent;
20602 foreach ($this->injectors
as $i => $injector) {
20603 if (isset(
$token->skip[$i])) {
20607 if (
$token->rewind !==
null &&
$token->rewind !== $i) {
20611 $injector->handleEnd($r);
20613 $this->stack[] = $current_parent;
20623 $this->stack[] = $current_parent;
20627 $size = count($this->stack);
20629 $skipped_tags =
false;
20630 for ($j = $size - 2; $j >= 0; $j--) {
20631 if ($this->stack[$j]->
name ==
$token->name) {
20632 $skipped_tags = array_slice($this->stack, $j);
20638 if ($skipped_tags ===
false) {
20639 if ($escape_invalid_tags) {
20641 $e->send(E_WARNING,
'Strategy_MakeWellFormed: Stray end tag to text');
20646 $e->send(E_WARNING,
'Strategy_MakeWellFormed: Stray end tag removed');
20648 $token = $this->
remove();
20655 $c = count($skipped_tags);
20657 for ($j = $c - 1; $j > 0; $j--) {
20660 if (!isset($skipped_tags[$j]->armor[
'MakeWellFormed_TagClosedError'])) {
20661 $e->send(E_NOTICE,
'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
20667 $replace = array(
$token);
20668 for ($j = 1; $j < $c; $j++) {
20671 $new_token->start = $skipped_tags[$j];
20672 array_unshift($replace, $new_token);
20673 if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
20675 $element = clone $skipped_tags[$j];
20676 $element->carryover =
true;
20677 $element->armor[
'MakeWellFormed_TagClosedError'] =
true;
20678 $replace[] = $element;
20686 $context->destroy(
'CurrentToken');
20687 $context->destroy(
'CurrentNesting');
20690 unset($this->injectors, $this->stack, $this->tokens);
20721 if (is_object(
$token)) {
20723 $token = array(1, $tmp);
20732 if (!is_array(
$token)) {
20735 if (!is_int(
$token[0])) {
20736 array_unshift(
$token, 1);
20745 $delete = array_shift(
$token);
20746 list($old, $r) = $this->zipper->splice($this->token, $delete,
$token);
20748 if ($injector > -1) {
20755 $oldskip = isset($old[0]) ? $old[0]->skip : array();
20757 $object->skip = $oldskip;
20758 $object->skip[$injector] =
true;
20771 private function insertBefore(
$token)
20775 $splice = $this->zipper->splice($this->token, 0, array(
$token));
20784 private function remove()
20786 return $this->zipper->delete();
20859 $definition =
$config->getHTMLDefinition();
20863 $escape_invalid_tags =
$config->get(
'Core.EscapeInvalidTags');
20864 $remove_invalid_img =
$config->get(
'Core.RemoveInvalidImg');
20867 $trusted =
$config->get(
'HTML.Trusted');
20868 $comment_lookup =
$config->get(
'HTML.AllowedComments');
20869 $comment_regexp =
$config->get(
'HTML.AllowedCommentsRegexp');
20870 $check_comments = $comment_lookup !== array() || $comment_regexp !==
null;
20872 $remove_script_contents =
$config->get(
'Core.RemoveScriptContents');
20873 $hidden_elements =
$config->get(
'Core.HiddenElements');
20876 if ($remove_script_contents ===
true) {
20877 $hidden_elements[
'script'] =
true;
20878 } elseif ($remove_script_contents ===
false && isset($hidden_elements[
'script'])) {
20879 unset($hidden_elements[
'script']);
20885 $remove_until =
false;
20888 $textify_comments =
false;
20891 $context->register(
'CurrentToken', $token);
20894 if (
$config->get(
'Core.CollectErrors')) {
20895 $e =& $context->get(
'ErrorCollector');
20898 foreach ($tokens
as $token) {
20899 if ($remove_until) {
20900 if (
empty($token->is_tag) || $token->name !== $remove_until) {
20904 if (!
empty($token->is_tag)) {
20908 if (isset($definition->info_tag_transform[$token->name])) {
20909 $original_name = $token->name;
20912 $token = $definition->
20913 info_tag_transform[$token->name]->transform($token,
$config, $context);
20915 $e->send(E_NOTICE,
'Strategy_RemoveForeignElements: Tag transform', $original_name);
20919 if (isset($definition->info[$token->name])) {
20923 $definition->info[$token->name]->required_attr &&
20924 ($token->name !=
'img' || $remove_invalid_img)
20926 $attr_validator->validateToken($token,
$config, $context);
20928 foreach ($definition->info[$token->name]->required_attr
as $name) {
20929 if (!isset($token->attr[$name])) {
20938 'Strategy_RemoveForeignElements: Missing required attribute',
20944 $token->armor[
'ValidateAttributes'] =
true;
20948 $textify_comments = $token->name;
20950 $textify_comments =
false;
20953 } elseif ($escape_invalid_tags) {
20956 $e->send(E_WARNING,
'Strategy_RemoveForeignElements: Foreign element to text');
20959 $generator->generateFromToken($token)
20964 if (isset($hidden_elements[$token->name])) {
20966 $remove_until = $token->name;
20970 $remove_until =
false;
20973 $e->send(E_ERROR,
'Strategy_RemoveForeignElements: Foreign meta element removed');
20977 $e->send(E_ERROR,
'Strategy_RemoveForeignElements: Foreign element removed');
20984 if ($textify_comments !==
false) {
20985 $data = $token->data;
20987 } elseif ($trusted || $check_comments) {
20989 $trailing_hyphen =
false;
20992 if (substr($token->data, -1) ==
'-') {
20993 $trailing_hyphen =
true;
20996 $token->data = rtrim($token->data,
'-');
20997 $found_double_hyphen =
false;
20998 while (strpos($token->data,
'--') !==
false) {
20999 $found_double_hyphen =
true;
21000 $token->data = str_replace(
'--',
'-', $token->data);
21002 if ($trusted || !
empty($comment_lookup[trim($token->data)]) ||
21003 ($comment_regexp !==
null && preg_match($comment_regexp, trim($token->data)))) {
21006 if ($trailing_hyphen) {
21009 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
21012 if ($found_double_hyphen) {
21013 $e->send(E_NOTICE,
'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
21018 $e->send(E_NOTICE,
'Strategy_RemoveForeignElements: Comment removed');
21025 $e->send(E_NOTICE,
'Strategy_RemoveForeignElements: Comment removed');
21033 $result[] = $token;
21035 if ($remove_until && $e) {
21037 $e->send(E_ERROR,
'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
21039 $context->destroy(
'CurrentToken');
21067 $context->register(
'CurrentToken', $token);
21069 foreach ($tokens
as $key => $token) {
21078 if (!
empty($token->armor[
'ValidateAttributes'])) {
21083 $validator->validateToken($token,
$config, $context);
21085 $context->destroy(
'CurrentToken');
21145 $new_tag = clone $tag;
21150 $attr = $tag->attr;
21151 $prepend_style =
'';
21154 if (isset($attr[
'color'])) {
21155 $prepend_style .=
'color:' . $attr[
'color'] .
';';
21156 unset($attr[
'color']);
21160 if (isset($attr[
'face'])) {
21161 $prepend_style .=
'font-family:' . $attr[
'face'] .
';';
21162 unset($attr[
'face']);
21166 if (isset($attr[
'size'])) {
21168 if ($attr[
'size'] !==
'') {
21169 if ($attr[
'size'][0] ==
'+' || $attr[
'size'][0] ==
'-') {
21170 $size = (int)$attr[
'size'];
21172 $attr[
'size'] =
'-2';
21175 $attr[
'size'] =
'+4';
21178 $size = (int)$attr[
'size'];
21180 $attr[
'size'] =
'7';
21184 if (isset($this->_size_lookup[$attr[
'size']])) {
21185 $prepend_style .=
'font-size:' .
21186 $this->_size_lookup[$attr[
'size']] .
';';
21188 unset($attr[
'size']);
21191 if ($prepend_style) {
21192 $attr[
'style'] = isset($attr[
'style']) ?
21193 $prepend_style . $attr[
'style'] :
21197 $new_tag = clone $tag;
21199 $new_tag->attr = $attr;
21239 $new_tag = clone $tag;
21241 if (!is_null($this->style) &&
21244 $this->
prependCSS($new_tag->attr, $this->style);
21279 $this->data =
$data;
21280 $this->line =
$line;
21335 foreach (
$attr as $key => $value) {
21337 if (!ctype_lower($key)) {
21338 $new_key = strtolower($key);
21339 if (!isset(
$attr[$new_key])) {
21342 if ($new_key !== $key) {
21343 unset(
$attr[$key]);
21347 $this->attr =
$attr;
21348 $this->line =
$line;
21368 $n = parent::toNode();
21395 throw new Exception(
"HTMLPurifier_Token_End->toNode not supported!");
21453 $this->data =
$data;
21454 $this->is_whitespace = ctype_space(
$data);
21455 $this->line =
$line;
21486 $our_host =
$config->getDefinition(
'URI')->host;
21487 if ($our_host !==
null) {
21488 $this->ourHostParts = array_reverse(explode(
'.', $our_host));
21500 if (is_null($uri->host)) {
21503 if ($this->ourHostParts ===
false) {
21506 $host_parts = array_reverse(explode(
'.', $uri->host));
21507 foreach ($this->ourHostParts
as $i => $x) {
21508 if (!isset($host_parts[$i])) {
21511 if ($host_parts[$i] != $this->ourHostParts[$i]) {
21538 if (!$context->get(
'EmbeddedURI',
true)) {
21541 return parent::filter($uri,
$config, $context);
21564 return !$context->get(
'EmbeddedURI',
true);
21594 $this->blacklist =
$config->get(
'URI.HostBlacklist');
21606 foreach ($this->blacklist
as $blacklisted_host_fragment) {
21607 if (strpos($uri->host, $blacklisted_host_fragment) !==
false) {
21645 $this->base =
$def->base;
21646 if (is_null($this->base)) {
21648 'URI.MakeAbsolute is being ignored due to lack of ' .
21649 'value for URI.Base configuration',
21654 $this->base->fragment =
null;
21655 $stack = explode(
'/', $this->base->path);
21657 $stack = $this->_collapseStack($stack);
21658 $this->basePathStack = $stack;
21670 if (is_null($this->base)) {
21673 if ($uri->path ===
'' && is_null($uri->scheme) &&
21674 is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
21679 if (!is_null($uri->scheme)) {
21681 if (!is_null($uri->host)) {
21684 $scheme_obj = $uri->getSchemeObj(
$config, $context);
21685 if (!$scheme_obj) {
21689 if (!$scheme_obj->hierarchical) {
21695 if (!is_null($uri->host)) {
21699 if ($uri->path ===
'') {
21700 $uri->path = $this->base->path;
21701 } elseif ($uri->path[0] !==
'/') {
21703 $stack = explode(
'/', $uri->path);
21704 $new_stack = array_merge($this->basePathStack, $stack);
21705 if ($new_stack[0] !==
'' && !is_null($this->base->host)) {
21706 array_unshift($new_stack,
'');
21708 $new_stack = $this->_collapseStack($new_stack);
21709 $uri->path = implode(
'/', $new_stack);
21712 $uri->path = implode(
'/', $this->_collapseStack(explode(
'/', $uri->path)));
21715 $uri->scheme = $this->base->scheme;
21716 if (is_null($uri->userinfo)) {
21717 $uri->userinfo = $this->base->userinfo;
21719 if (is_null($uri->host)) {
21720 $uri->host = $this->base->host;
21722 if (is_null($uri->port)) {
21723 $uri->port = $this->base->port;
21733 private function _collapseStack($stack)
21736 $is_folder =
false;
21737 for ($i = 0; isset($stack[$i]); $i++) {
21738 $is_folder =
false;
21740 if ($stack[$i] ==
'' && $i && isset($stack[$i + 1])) {
21743 if ($stack[$i] ==
'..') {
21744 if (!
empty($result)) {
21745 $segment = array_pop($result);
21746 if ($segment ===
'' &&
empty($result)) {
21750 } elseif ($segment ===
'..') {
21760 if ($stack[$i] ==
'.') {
21765 $result[] = $stack[$i];
21808 private $secretKey;
21821 $this->target =
$config->get(
'URI.' . $this->
name);
21823 $this->doEmbed =
$config->get(
'URI.MungeResources');
21824 $this->secretKey =
$config->get(
'URI.MungeSecretKey');
21825 if ($this->secretKey && !function_exists(
'hash_hmac')) {
21826 throw new Exception(
"Cannot use %URI.MungeSecretKey without hash_hmac support.");
21839 if ($context->get(
'EmbeddedURI',
true) && !$this->doEmbed) {
21843 $scheme_obj = $uri->getSchemeObj(
$config, $context);
21844 if (!$scheme_obj) {
21847 if (!$scheme_obj->browsable) {
21850 if ($uri->isBenign(
$config, $context)) {
21855 $this->replace = array_map(
'rawurlencode', $this->replace);
21857 $new_uri = strtr($this->target, $this->replace);
21858 $new_uri = $this->parser->parse($new_uri);
21861 if ($uri->host === $new_uri->host) {
21875 $string = $uri->toString();
21877 $this->replace[
'%s'] = $string;
21878 $this->replace[
'%r'] = $context->get(
'EmbeddedURI',
true);
21879 $token = $context->get(
'CurrentToken',
true);
21880 $this->replace[
'%n'] = $token ? $token->name :
null;
21881 $this->replace[
'%m'] = $context->get(
'CurrentAttr',
true);
21882 $this->replace[
'%p'] = $context->get(
'CurrentCSSProperty',
true);
21884 if ($this->secretKey) {
21885 $this->replace[
'%t'] = hash_hmac(
"sha256", $string, $this->secretKey);
21926 $this->regexp =
$config->get(
'URI.SafeIframeRegexp');
21939 if (!
$config->get(
'HTML.SafeIframe')) {
21943 if (!$context->get(
'EmbeddedURI',
true)) {
21946 $token = $context->get(
'CurrentToken',
true);
21947 if (!($token && $token->name ==
'iframe')) {
21951 if ($this->regexp ===
null) {
21955 return preg_match($this->regexp, $uri->toString());
21979 'image/jpeg' =>
true,
21980 'image/gif' =>
true,
21981 'image/png' =>
true,
21998 $result = explode(
',', $uri->path, 2);
21999 $is_base64 =
false;
22001 $content_type =
null;
22002 if (count($result) == 2) {
22003 list($metadata, $data) = $result;
22005 $metas = explode(
';', $metadata);
22006 while (!
empty($metas)) {
22007 $cur = array_shift($metas);
22008 if ($cur ==
'base64') {
22012 if (substr($cur, 0, 8) ==
'charset=') {
22015 if ($charset !==
null) {
22018 $charset = substr($cur, 8);
22020 if ($content_type !==
null) {
22023 $content_type = $cur;
22027 $data = $result[0];
22029 if ($content_type !==
null &&
empty($this->allowed_types[$content_type])) {
22032 if ($charset !==
null) {
22036 $data = rawurldecode($data);
22038 $raw_data = base64_decode($data);
22042 if ( strlen($raw_data) < 12 ) {
22049 if (function_exists(
'sys_get_temp_dir')) {
22050 $file = tempnam(sys_get_temp_dir(),
"");
22052 $file = tempnam(
"/tmp",
"");
22054 file_put_contents($file, $raw_data);
22055 if (function_exists(
'exif_imagetype')) {
22056 $image_code = exif_imagetype($file);
22058 } elseif (function_exists(
'getimagesize')) {
22059 set_error_handler(array($this,
'muteErrorHandler'));
22060 $info = getimagesize($file);
22061 restore_error_handler();
22063 if ($info ==
false) {
22066 $image_code = $info[2];
22068 trigger_error(
"could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
22070 $real_content_type = image_type_to_mime_type($image_code);
22071 if ($real_content_type != $content_type) {
22074 if (
empty($this->allowed_types[$real_content_type])) {
22077 $content_type = $real_content_type;
22080 $uri->userinfo =
null;
22083 $uri->fragment =
null;
22084 $uri->query =
null;
22085 $uri->path =
"$content_type;base64," . base64_encode($raw_data);
22131 $uri->userinfo =
null;
22136 $uri->query =
null;
22173 $uri->query =
null;
22176 $semicolon_pos = strrpos($uri->path,
';');
22177 if ($semicolon_pos !==
false) {
22178 $type = substr($uri->path, $semicolon_pos + 1);
22179 $uri->path = substr($uri->path, 0, $semicolon_pos);
22181 if (strpos($type,
'=') !==
false) {
22183 list($key, $typecode) = explode(
'=', $type, 2);
22184 if ($key !==
'type') {
22186 $uri->path .=
'%3B' . $type;
22187 } elseif ($typecode ===
'a' || $typecode ===
'i' || $typecode ===
'd') {
22188 $type_ret =
";type=$typecode";
22191 $uri->path .=
'%3B' . $type;
22193 $uri->path = str_replace(
';',
'%3B', $uri->path);
22194 $uri->path .= $type_ret;
22232 $uri->userinfo =
null;
22289 $uri->userinfo =
null;
22324 $uri->userinfo =
null;
22327 $uri->query =
null;
22360 $uri->userinfo =
null;
22361 $uri->query =
null;
22399 $uri->userinfo =
null;
22405 $uri->path = preg_replace(
'/(?!^\+)[^\dx]/',
'',
22407 str_replace(
'X',
'x', $uri->path));
22433 if ($allow_null && $var ===
null) {
22447 if (is_string($var) && ctype_digit($var)) {
22452 if ((is_string($var) && is_numeric($var)) || is_int($var)) {
22453 $var = (float)$var;
22457 if (is_int($var) && ($var === 0 || $var === 1)) {
22459 } elseif (is_string($var)) {
22460 if ($var ==
'on' || $var ==
'true' || $var ==
'1') {
22462 } elseif ($var ==
'off' || $var ==
'false' || $var ==
'0') {
22472 if (is_string($var)) {
22479 if (strpos($var,
"\n") ===
false && strpos($var,
"\r") ===
false) {
22482 $var = explode(
',', $var);
22484 $var = preg_split(
'/(,|[\n\r]+)/', $var);
22487 foreach ($var
as $i => $j) {
22488 $var[$i] = trim($j);
22490 if ($type === self::HASH) {
22493 foreach ($var
as $keypair) {
22494 $c = explode(
':', $keypair, 2);
22495 if (!isset($c[1])) {
22498 $nvar[trim($c[0])] = trim($c[1]);
22503 if (!is_array($var)) {
22506 $keys = array_keys($var);
22507 if ($keys === array_keys($keys)) {
22508 if ($type == self::ALIST) {
22510 } elseif ($type == self::LOOKUP) {
22512 foreach ($var
as $key) {
22520 if ($type === self::ALIST) {
22521 trigger_error(
"Array list did not have consecutive integer indexes", E_USER_WARNING);
22522 return array_values($var);
22524 if ($type === self::LOOKUP) {
22525 foreach ($var
as $key => $value) {
22526 if ($value !==
true) {
22528 "Lookup array has non-true value at key '$key'; " .
22529 "maybe your input array was not indexed numerically",
22575 $result = eval(
"\$var = $expr;");
22576 if ($result ===
false) {