PageRenderTime 50ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 1ms

/system/guard/HTMLPurifier.php

https://github.com/daqing/aeolus
PHP | 7165 lines | 7140 code | 11 blank | 14 comment | 1389 complexity | b19b343e79634ba3a38a8d8cb07b31a0 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. class HTMLPurifier
  3. {
  4. public $version = '3.1.0';
  5. const VERSION = '3.1.0';
  6. public $config;
  7. private $filters = array();
  8. private static $instance;
  9. protected $strategy, $generator;
  10. public $context;
  11. public function __construct($config = null) {
  12. $this->config = HTMLPurifier_Config::create($config);
  13. $this->strategy = new HTMLPurifier_Strategy_Core();
  14. }
  15. public function addFilter($filter) {
  16. trigger_error('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom', E_USER_WARNING);
  17. $this->filters[] = $filter;
  18. }
  19. public function purify($html, $config = null) {
  20. $config = $config ? HTMLPurifier_Config::create($config) : $this->config;
  21. $lexer = HTMLPurifier_Lexer::create($config);
  22. $context = new HTMLPurifier_Context();
  23. $this->generator = new HTMLPurifier_Generator($config, $context);
  24. $context->register('Generator', $this->generator);
  25. if ($config->get('Core', 'CollectErrors')) {
  26. $language_factory = HTMLPurifier_LanguageFactory::instance();
  27. $language = $language_factory->create($config, $context);
  28. $context->register('Locale', $language);
  29. $error_collector = new HTMLPurifier_ErrorCollector($context);
  30. $context->register('ErrorCollector', $error_collector);
  31. }
  32. $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
  33. $context->register('IDAccumulator', $id_accumulator);
  34. $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context);
  35. $filter_flags = $config->getBatch('Filter');
  36. $custom_filters = $filter_flags['Custom'];
  37. unset($filter_flags['Custom']);
  38. $filters = array();
  39. foreach ($filter_flags as $filter => $flag) {
  40. if (!$flag) continue;
  41. $class = "HTMLPurifier_Filter_$filter";
  42. $filters[] = new $class;
  43. }
  44. foreach ($custom_filters as $filter) {
  45. $filters[] = $filter;
  46. }
  47. $filters = array_merge($filters, $this->filters);
  48. for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) {
  49. $html = $filters[$i]->preFilter($html, $config, $context);
  50. }
  51. $html =
  52. $this->generator->generateFromTokens(
  53. $this->strategy->execute(
  54. $lexer->tokenizeHTML(
  55. $html, $config, $context
  56. ),
  57. $config, $context
  58. )
  59. );
  60. for ($i = $filter_size - 1; $i >= 0; $i--) {
  61. $html = $filters[$i]->postFilter($html, $config, $context);
  62. }
  63. $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context);
  64. $this->context =& $context;
  65. return $html;
  66. }
  67. public function purifyArray($array_of_html, $config = null) {
  68. $context_array = array();
  69. foreach ($array_of_html as $key => $html) {
  70. $array_of_html[$key] = $this->purify($html, $config);
  71. $context_array[$key] = $this->context;
  72. }
  73. $this->context = $context_array;
  74. return $array_of_html;
  75. }
  76. public static function instance($prototype = null) {
  77. if (!self::$instance || $prototype) {
  78. if ($prototype instanceof HTMLPurifier) {
  79. self::$instance = $prototype;
  80. } elseif ($prototype) {
  81. self::$instance = new HTMLPurifier($prototype);
  82. } else {
  83. self::$instance = new HTMLPurifier();
  84. }
  85. }
  86. return self::$instance;
  87. }
  88. public static function getInstance($prototype = null) {
  89. return HTMLPurifier::instance($prototype);
  90. }
  91. }
  92. class HTMLPurifier_AttrCollections
  93. {
  94. public $info = array();
  95. public function __construct($attr_types, $modules) {
  96. foreach ($modules as $module) {
  97. foreach ($module->attr_collections as $coll_i => $coll) {
  98. if (!isset($this->info[$coll_i])) {
  99. $this->info[$coll_i] = array();
  100. }
  101. foreach ($coll as $attr_i => $attr) {
  102. if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
  103. $this->info[$coll_i][$attr_i] = array_merge(
  104. $this->info[$coll_i][$attr_i], $attr);
  105. continue;
  106. }
  107. $this->info[$coll_i][$attr_i] = $attr;
  108. }
  109. }
  110. }
  111. foreach ($this->info as $name => $attr) {
  112. $this->performInclusions($this->info[$name]);
  113. $this->expandIdentifiers($this->info[$name], $attr_types);
  114. }
  115. }
  116. public function performInclusions(&$attr) {
  117. if (!isset($attr[0])) return;
  118. $merge = $attr[0];
  119. $seen = array();
  120. for ($i = 0; isset($merge[$i]); $i++) {
  121. if (isset($seen[$merge[$i]])) continue;
  122. $seen[$merge[$i]] = true;
  123. if (!isset($this->info[$merge[$i]])) continue;
  124. foreach ($this->info[$merge[$i]] as $key => $value) {
  125. if (isset($attr[$key])) continue;
  126. $attr[$key] = $value;
  127. }
  128. if (isset($this->info[$merge[$i]][0])) {
  129. $merge = array_merge($merge, $this->info[$merge[$i]][0]);
  130. }
  131. }
  132. unset($attr[0]);
  133. }
  134. public function expandIdentifiers(&$attr, $attr_types) {
  135. $processed = array();
  136. foreach ($attr as $def_i => $def) {
  137. if ($def_i === 0) continue;
  138. if (isset($processed[$def_i])) continue;
  139. if ($required = (strpos($def_i, '*') !== false)) {
  140. unset($attr[$def_i]);
  141. $def_i = trim($def_i, '*');
  142. $attr[$def_i] = $def;
  143. }
  144. $processed[$def_i] = true;
  145. if (is_object($def)) {
  146. $attr[$def_i]->required = ($required || $attr[$def_i]->required);
  147. continue;
  148. }
  149. if ($def === false) {
  150. unset($attr[$def_i]);
  151. continue;
  152. }
  153. if ($t = $attr_types->get($def)) {
  154. $attr[$def_i] = $t;
  155. $attr[$def_i]->required = $required;
  156. } else {
  157. unset($attr[$def_i]);
  158. }
  159. }
  160. }
  161. }
  162. abstract class HTMLPurifier_AttrDef
  163. {
  164. public $minimized = false;
  165. public $required = false;
  166. abstract public function validate($string, $config, $context);
  167. public function parseCDATA($string) {
  168. $string = trim($string);
  169. $string = str_replace("\n", '', $string);
  170. $string = str_replace(array("\r", "\t"), ' ', $string);
  171. return $string;
  172. }
  173. public function make($string) {
  174. return $this;
  175. }
  176. protected function mungeRgb($string) {
  177. return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
  178. }
  179. }
  180. abstract class HTMLPurifier_AttrTransform
  181. {
  182. abstract public function transform($attr, $config, $context);
  183. public function prependCSS(&$attr, $css) {
  184. $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  185. $attr['style'] = $css . $attr['style'];
  186. }
  187. public function confiscateAttr(&$attr, $key) {
  188. if (!isset($attr[$key])) return null;
  189. $value = $attr[$key];
  190. unset($attr[$key]);
  191. return $value;
  192. }
  193. }
  194. class HTMLPurifier_AttrTypes
  195. {
  196. protected $info = array();
  197. public function __construct() {
  198. $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum();
  199. $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool();
  200. $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text();
  201. $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID();
  202. $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length();
  203. $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
  204. $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
  205. $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels();
  206. $this->info['Text'] = new HTMLPurifier_AttrDef_Text();
  207. $this->info['URI'] = new HTMLPurifier_AttrDef_URI();
  208. $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
  209. $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
  210. $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
  211. $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
  212. }
  213. public function get($type) {
  214. if (strpos($type, '#') !== false) list($type, $string) = explode('#', $type, 2);
  215. else $string = '';
  216. if (!isset($this->info[$type])) {
  217. trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
  218. return;
  219. }
  220. return $this->info[$type]->make($string);
  221. }
  222. public function set($type, $impl) {
  223. $this->info[$type] = $impl;
  224. }
  225. }
  226. class HTMLPurifier_AttrValidator
  227. {
  228. public function validateToken(&$token, &$config, $context) {
  229. $definition = $config->getHTMLDefinition();
  230. $e =& $context->get('ErrorCollector', true);
  231. $ok =& $context->get('IDAccumulator', true);
  232. if (!$ok) {
  233. $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
  234. $context->register('IDAccumulator', $id_accumulator);
  235. }
  236. $current_token =& $context->get('CurrentToken', true);
  237. if (!$current_token) $context->register('CurrentToken', $token);
  238. if (
  239. !$token instanceof HTMLPurifier_Token_Start &&
  240. !$token instanceof HTMLPurifier_Token_Empty
  241. ) return $token;
  242. $d_defs = $definition->info_global_attr;
  243. $attr =& $token->attr;
  244. foreach ($definition->info_attr_transform_pre as $transform) {
  245. $attr = $transform->transform($o = $attr, $config, $context);
  246. if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  247. }
  248. foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
  249. $attr = $transform->transform($o = $attr, $config, $context);
  250. if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  251. }
  252. $defs = $definition->info[$token->name]->attr;
  253. $attr_key = false;
  254. $context->register('CurrentAttr', $attr_key);
  255. foreach ($attr as $attr_key => $value) {
  256. if ( isset($defs[$attr_key]) ) {
  257. if ($defs[$attr_key] === false) {
  258. $result = false;
  259. } else {
  260. $result = $defs[$attr_key]->validate(
  261. $value, $config, $context
  262. );
  263. }
  264. } elseif ( isset($d_defs[$attr_key]) ) {
  265. $result = $d_defs[$attr_key]->validate(
  266. $value, $config, $context
  267. );
  268. } else {
  269. $result = false;
  270. }
  271. if ($result === false || $result === null) {
  272. if ($e) $e->send(E_ERROR, 'AttrValidator: Attribute removed');
  273. unset($attr[$attr_key]);
  274. } elseif (is_string($result)) {
  275. $attr[$attr_key] = $result;
  276. }
  277. }
  278. $context->destroy('CurrentAttr');
  279. foreach ($definition->info_attr_transform_post as $transform) {
  280. $attr = $transform->transform($o = $attr, $config, $context);
  281. if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  282. }
  283. foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
  284. $attr = $transform->transform($o = $attr, $config, $context);
  285. if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  286. }
  287. if (!$current_token) $context->destroy('CurrentToken');
  288. }
  289. }
  290. if (!defined('HTMLPURIFIER_PREFIX')) {
  291. define('HTMLPURIFIER_PREFIX', dirname(__FILE__));
  292. set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());
  293. }
  294. if (!defined('PHP_EOL')) {
  295. switch (strtoupper(substr(PHP_OS, 0, 3))) {
  296. case 'WIN':
  297. define('PHP_EOL', "\r\n");
  298. break;
  299. case 'DAR':
  300. define('PHP_EOL', "\r");
  301. break;
  302. default:
  303. define('PHP_EOL', "\n");
  304. }
  305. }
  306. class HTMLPurifier_Bootstrap
  307. {
  308. public static function autoload($class) {
  309. $file = HTMLPurifier_Bootstrap::getPath($class);
  310. if (!$file) return false;
  311. require HTMLPURIFIER_PREFIX . '/' . $file;
  312. return true;
  313. }
  314. public static function getPath($class) {
  315. if (strncmp('HTMLPurifier', $class, 12) !== 0) return false;
  316. if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
  317. $code = str_replace('_', '-', substr($class, 22));
  318. $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
  319. } else {
  320. $file = str_replace('_', '/', $class) . '.php';
  321. }
  322. if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) return false;
  323. return $file;
  324. }
  325. public static function registerAutoload() {
  326. $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
  327. if ( ($funcs = spl_autoload_functions()) === false ) {
  328. spl_autoload_register($autoload);
  329. } elseif (function_exists('spl_autoload_unregister')) {
  330. $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
  331. version_compare(PHP_VERSION, '5.1.0', '>=');
  332. foreach ($funcs as $func) {
  333. if (is_array($func)) {
  334. $reflector = new ReflectionMethod($func[0], $func[1]);
  335. if (!$reflector->isStatic()) {
  336. throw new Exception('
  337. HTML Purifier autoloader registrar is not compatible
  338. with non-static object methods due to PHP Bug #44144;
  339. Please do not use HTMLPurifier.autoload.php (or any
  340. file that includes this file); instead, place the code:
  341. spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
  342. after your own autoloaders.
  343. ');
  344. }
  345. if ($compat) $func = implode('::', $func);
  346. }
  347. spl_autoload_unregister($func);
  348. }
  349. spl_autoload_register($autoload);
  350. foreach ($funcs as $func) spl_autoload_register($func);
  351. }
  352. }
  353. }
  354. abstract class HTMLPurifier_Definition
  355. {
  356. public $setup = false;
  357. public $type;
  358. abstract protected function doSetup($config);
  359. public function setup($config) {
  360. if ($this->setup) return;
  361. $this->setup = true;
  362. $this->doSetup($config);
  363. }
  364. }
  365. class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
  366. {
  367. public $type = 'CSS';
  368. public $info = array();
  369. protected function doSetup($config) {
  370. $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
  371. array('left', 'right', 'center', 'justify'), false);
  372. $border_style =
  373. $this->info['border-bottom-style'] =
  374. $this->info['border-right-style'] =
  375. $this->info['border-left-style'] =
  376. $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
  377. array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double',
  378. 'groove', 'ridge', 'inset', 'outset'), false);
  379. $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
  380. $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
  381. array('none', 'left', 'right', 'both'), false);
  382. $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
  383. array('none', 'left', 'right'), false);
  384. $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
  385. array('normal', 'italic', 'oblique'), false);
  386. $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
  387. array('normal', 'small-caps'), false);
  388. $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
  389. array(
  390. new HTMLPurifier_AttrDef_Enum(array('none')),
  391. new HTMLPurifier_AttrDef_CSS_URI()
  392. )
  393. );
  394. $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
  395. array('inside', 'outside'), false);
  396. $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
  397. array('disc', 'circle', 'square', 'decimal', 'lower-roman',
  398. 'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false);
  399. $this->info['list-style-image'] = $uri_or_none;
  400. $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
  401. $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
  402. array('capitalize', 'uppercase', 'lowercase', 'none'), false);
  403. $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
  404. $this->info['background-image'] = $uri_or_none;
  405. $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
  406. array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
  407. );
  408. $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
  409. array('scroll', 'fixed')
  410. );
  411. $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
  412. $border_color =
  413. $this->info['border-top-color'] =
  414. $this->info['border-bottom-color'] =
  415. $this->info['border-left-color'] =
  416. $this->info['border-right-color'] =
  417. $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  418. new HTMLPurifier_AttrDef_Enum(array('transparent')),
  419. new HTMLPurifier_AttrDef_CSS_Color()
  420. ));
  421. $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
  422. $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
  423. $border_width =
  424. $this->info['border-top-width'] =
  425. $this->info['border-bottom-width'] =
  426. $this->info['border-left-width'] =
  427. $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  428. new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
  429. new HTMLPurifier_AttrDef_CSS_Length(true)
  430. ));
  431. $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
  432. $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  433. new HTMLPurifier_AttrDef_Enum(array('normal')),
  434. new HTMLPurifier_AttrDef_CSS_Length()
  435. ));
  436. $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  437. new HTMLPurifier_AttrDef_Enum(array('normal')),
  438. new HTMLPurifier_AttrDef_CSS_Length()
  439. ));
  440. $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  441. new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small',
  442. 'small', 'medium', 'large', 'x-large', 'xx-large',
  443. 'larger', 'smaller')),
  444. new HTMLPurifier_AttrDef_CSS_Percentage(),
  445. new HTMLPurifier_AttrDef_CSS_Length()
  446. ));
  447. $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  448. new HTMLPurifier_AttrDef_Enum(array('normal')),
  449. new HTMLPurifier_AttrDef_CSS_Number(true),
  450. new HTMLPurifier_AttrDef_CSS_Length(true),
  451. new HTMLPurifier_AttrDef_CSS_Percentage(true)
  452. ));
  453. $margin =
  454. $this->info['margin-top'] =
  455. $this->info['margin-bottom'] =
  456. $this->info['margin-left'] =
  457. $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  458. new HTMLPurifier_AttrDef_CSS_Length(),
  459. new HTMLPurifier_AttrDef_CSS_Percentage(),
  460. new HTMLPurifier_AttrDef_Enum(array('auto'))
  461. ));
  462. $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
  463. $padding =
  464. $this->info['padding-top'] =
  465. $this->info['padding-bottom'] =
  466. $this->info['padding-left'] =
  467. $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  468. new HTMLPurifier_AttrDef_CSS_Length(true),
  469. new HTMLPurifier_AttrDef_CSS_Percentage(true)
  470. ));
  471. $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
  472. $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  473. new HTMLPurifier_AttrDef_CSS_Length(),
  474. new HTMLPurifier_AttrDef_CSS_Percentage()
  475. ));
  476. $this->info['width'] =
  477. $this->info['height'] =
  478. new HTMLPurifier_AttrDef_CSS_DenyElementDecorator(
  479. new HTMLPurifier_AttrDef_CSS_Composite(array(
  480. new HTMLPurifier_AttrDef_CSS_Length(true),
  481. new HTMLPurifier_AttrDef_CSS_Percentage(true),
  482. new HTMLPurifier_AttrDef_Enum(array('auto'))
  483. )), 'img');
  484. $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
  485. $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
  486. $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
  487. array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300',
  488. '400', '500', '600', '700', '800', '900'), false);
  489. $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
  490. $this->info['border'] =
  491. $this->info['border-bottom'] =
  492. $this->info['border-top'] =
  493. $this->info['border-left'] =
  494. $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
  495. $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array(
  496. 'collapse', 'separate'));
  497. $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array(
  498. 'top', 'bottom'));
  499. $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array(
  500. 'auto', 'fixed'));
  501. $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
  502. new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super',
  503. 'top', 'text-top', 'middle', 'bottom', 'text-bottom')),
  504. new HTMLPurifier_AttrDef_CSS_Length(),
  505. new HTMLPurifier_AttrDef_CSS_Percentage()
  506. ));
  507. $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
  508. $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap'));
  509. if ($config->get('CSS', 'Proprietary')) {
  510. $this->doSetupProprietary($config);
  511. }
  512. if ($config->get('CSS', 'AllowTricky')) {
  513. $this->doSetupTricky($config);
  514. }
  515. $allow_important = $config->get('CSS', 'AllowImportant');
  516. foreach ($this->info as $k => $v) {
  517. $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
  518. }
  519. $this->setupConfigStuff($config);
  520. }
  521. protected function doSetupProprietary($config) {
  522. $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  523. $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  524. $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  525. $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  526. $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  527. $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  528. $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  529. $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  530. $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  531. $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
  532. }
  533. protected function doSetupTricky($config) {
  534. $this->info['display'] = new HTMLPurifier_AttrDef_Enum(array(
  535. 'inline', 'block', 'list-item', 'run-in', 'compact',
  536. 'marker', 'table', 'inline-table', 'table-row-group',
  537. 'table-header-group', 'table-footer-group', 'table-row',
  538. 'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none'
  539. ));
  540. $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array(
  541. 'visible', 'hidden', 'collapse'
  542. ));
  543. }
  544. protected function setupConfigStuff($config) {
  545. $support = "(for information on implementing this, see the ".
  546. "support forums) ";
  547. $allowed_attributes = $config->get('CSS', 'AllowedProperties');
  548. if ($allowed_attributes !== null) {
  549. foreach ($this->info as $name => $d) {
  550. if(!isset($allowed_attributes[$name])) unset($this->info[$name]);
  551. unset($allowed_attributes[$name]);
  552. }
  553. foreach ($allowed_attributes as $name => $d) {
  554. $name = htmlspecialchars($name);
  555. trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
  556. }
  557. }
  558. }
  559. }
  560. abstract class HTMLPurifier_ChildDef
  561. {
  562. public $type;
  563. public $allow_empty;
  564. public $elements = array();
  565. abstract public function validateChildren($tokens_of_children, $config, $context);
  566. }
  567. class HTMLPurifier_Config
  568. {
  569. public $version = '3.1.0';
  570. public $autoFinalize = true;
  571. protected $serials = array();
  572. protected $serial;
  573. protected $conf;
  574. protected $parser;
  575. public $def;
  576. protected $definitions;
  577. protected $finalized = false;
  578. public function __construct($definition) {
  579. $this->conf = $definition->defaults;
  580. $this->def = $definition;
  581. $this->parser = new HTMLPurifier_VarParser_Flexible();
  582. }
  583. public static function create($config, $schema = null) {
  584. if ($config instanceof HTMLPurifier_Config) {
  585. return $config;
  586. }
  587. if (!$schema) {
  588. $ret = HTMLPurifier_Config::createDefault();
  589. } else {
  590. $ret = new HTMLPurifier_Config($schema);
  591. }
  592. if (is_string($config)) $ret->loadIni($config);
  593. elseif (is_array($config)) $ret->loadArray($config);
  594. return $ret;
  595. }
  596. public static function createDefault() {
  597. $definition = HTMLPurifier_ConfigSchema::instance();
  598. $config = new HTMLPurifier_Config($definition);
  599. return $config;
  600. }
  601. public function get($namespace, $key) {
  602. if (!$this->finalized && $this->autoFinalize) $this->finalize();
  603. if (!isset($this->def->info[$namespace][$key])) {
  604. trigger_error('Cannot retrieve value of undefined directive ' . htmlspecialchars("$namespace.$key"),
  605. E_USER_WARNING);
  606. return;
  607. }
  608. if ($this->def->info[$namespace][$key]->class == 'alias') {
  609. $d = $this->def->info[$namespace][$key];
  610. trigger_error('Cannot get value from aliased directive, use real name ' . $d->namespace . '.' . $d->name,
  611. E_USER_ERROR);
  612. return;
  613. }
  614. return $this->conf[$namespace][$key];
  615. }
  616. public function getBatch($namespace) {
  617. if (!$this->finalized && $this->autoFinalize) $this->finalize();
  618. if (!isset($this->def->info[$namespace])) {
  619. trigger_error('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace),
  620. E_USER_WARNING);
  621. return;
  622. }
  623. return $this->conf[$namespace];
  624. }
  625. public function getBatchSerial($namespace) {
  626. if (empty($this->serials[$namespace])) {
  627. $batch = $this->getBatch($namespace);
  628. unset($batch['DefinitionRev']);
  629. $this->serials[$namespace] = md5(serialize($batch));
  630. }
  631. return $this->serials[$namespace];
  632. }
  633. public function getSerial() {
  634. if (empty($this->serial)) {
  635. $this->serial = md5(serialize($this->getAll()));
  636. }
  637. return $this->serial;
  638. }
  639. public function getAll() {
  640. if (!$this->finalized && $this->autoFinalize) $this->finalize();
  641. return $this->conf;
  642. }
  643. public function set($namespace, $key, $value, $from_alias = false) {
  644. if ($this->isFinalized('Cannot set directive after finalization')) return;
  645. if (!isset($this->def->info[$namespace][$key])) {
  646. trigger_error('Cannot set undefined directive ' . htmlspecialchars("$namespace.$key") . ' to value',
  647. E_USER_WARNING);
  648. return;
  649. }
  650. if ($this->def->info[$namespace][$key]->class == 'alias') {
  651. if ($from_alias) {
  652. trigger_error('Double-aliases not allowed, please fix '.
  653. 'ConfigSchema bug with' . "$namespace.$key", E_USER_ERROR);
  654. return;
  655. }
  656. $this->set($new_ns = $this->def->info[$namespace][$key]->namespace,
  657. $new_dir = $this->def->info[$namespace][$key]->name,
  658. $value, true);
  659. trigger_error("$namespace.$key is an alias, preferred directive name is $new_ns.$new_dir", E_USER_NOTICE);
  660. return;
  661. }
  662. try {
  663. $value = $this->parser->parse(
  664. $value,
  665. $type = $this->def->info[$namespace][$key]->type,
  666. $this->def->info[$namespace][$key]->allow_null
  667. );
  668. } catch (HTMLPurifier_VarParserException $e) {
  669. trigger_error('Value for ' . "$namespace.$key" . ' is of invalid type, should be ' . $type, E_USER_WARNING);
  670. return;
  671. }
  672. if (is_string($value)) {
  673. if (isset($this->def->info[$namespace][$key]->aliases[$value])) {
  674. $value = $this->def->info[$namespace][$key]->aliases[$value];
  675. }
  676. if ($this->def->info[$namespace][$key]->allowed !== true) {
  677. if (!isset($this->def->info[$namespace][$key]->allowed[$value])) {
  678. trigger_error('Value not supported, valid values are: ' .
  679. $this->_listify($this->def->info[$namespace][$key]->allowed), E_USER_WARNING);
  680. return;
  681. }
  682. }
  683. }
  684. $this->conf[$namespace][$key] = $value;
  685. if ($namespace == 'HTML' || $namespace == 'CSS') {
  686. $this->definitions[$namespace] = null;
  687. }
  688. $this->serials[$namespace] = false;
  689. }
  690. private function _listify($lookup) {
  691. $list = array();
  692. foreach ($lookup as $name => $b) $list[] = $name;
  693. return implode(', ', $list);
  694. }
  695. public function getHTMLDefinition($raw = false) {
  696. return $this->getDefinition('HTML', $raw);
  697. }
  698. public function getCSSDefinition($raw = false) {
  699. return $this->getDefinition('CSS', $raw);
  700. }
  701. public function getDefinition($type, $raw = false) {
  702. if (!$this->finalized && $this->autoFinalize) $this->finalize();
  703. $factory = HTMLPurifier_DefinitionCacheFactory::instance();
  704. $cache = $factory->create($type, $this);
  705. if (!$raw) {
  706. if (!empty($this->definitions[$type])) {
  707. if (!$this->definitions[$type]->setup) {
  708. $this->definitions[$type]->setup($this);
  709. $cache->set($this->definitions[$type], $this);
  710. }
  711. return $this->definitions[$type];
  712. }
  713. $this->definitions[$type] = $cache->get($this);
  714. if ($this->definitions[$type]) {
  715. return $this->definitions[$type];
  716. }
  717. } elseif (
  718. !empty($this->definitions[$type]) &&
  719. !$this->definitions[$type]->setup
  720. ) {
  721. return $this->definitions[$type];
  722. }
  723. if ($type == 'HTML') {
  724. $this->definitions[$type] = new HTMLPurifier_HTMLDefinition();
  725. } elseif ($type == 'CSS') {
  726. $this->definitions[$type] = new HTMLPurifier_CSSDefinition();
  727. } elseif ($type == 'URI') {
  728. $this->definitions[$type] = new HTMLPurifier_URIDefinition();
  729. } else {
  730. throw new HTMLPurifier_Exception("Definition of $type type not supported");
  731. }
  732. if ($raw) {
  733. if (is_null($this->get($type, 'DefinitionID'))) {
  734. throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID");
  735. }
  736. return $this->definitions[$type];
  737. }
  738. $this->definitions[$type]->setup($this);
  739. $cache->set($this->definitions[$type], $this);
  740. return $this->definitions[$type];
  741. }
  742. public function loadArray($config_array) {
  743. if ($this->isFinalized('Cannot load directives after finalization')) return;
  744. foreach ($config_array as $key => $value) {
  745. $key = str_replace('_', '.', $key);
  746. if (strpos($key, '.') !== false) {
  747. list($namespace, $directive) = explode('.', $key);
  748. $this->set($namespace, $directive, $value);
  749. } else {
  750. $namespace = $key;
  751. $namespace_values = $value;
  752. foreach ($namespace_values as $directive => $value) {
  753. $this->set($namespace, $directive, $value);
  754. }
  755. }
  756. }
  757. }
  758. public static function getAllowedDirectivesForForm($allowed, $schema = null) {
  759. if (!$schema) {
  760. $schema = HTMLPurifier_ConfigSchema::instance();
  761. }
  762. if ($allowed !== true) {
  763. if (is_string($allowed)) $allowed = array($allowed);
  764. $allowed_ns = array();
  765. $allowed_directives = array();
  766. $blacklisted_directives = array();
  767. foreach ($allowed as $ns_or_directive) {
  768. if (strpos($ns_or_directive, '.') !== false) {
  769. if ($ns_or_directive[0] == '-') {
  770. $blacklisted_directives[substr($ns_or_directive, 1)] = true;
  771. } else {
  772. $allowed_directives[$ns_or_directive] = true;
  773. }
  774. } else {
  775. $allowed_ns[$ns_or_directive] = true;
  776. }
  777. }
  778. }
  779. $ret = array();
  780. foreach ($schema->info as $ns => $keypairs) {
  781. foreach ($keypairs as $directive => $def) {
  782. if ($allowed !== true) {
  783. if (isset($blacklisted_directives["$ns.$directive"])) continue;
  784. if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
  785. }
  786. if ($def->class == 'alias') continue;
  787. if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
  788. $ret[] = array($ns, $directive);
  789. }
  790. }
  791. return $ret;
  792. }
  793. public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
  794. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
  795. $config = HTMLPurifier_Config::create($ret, $schema);
  796. return $config;
  797. }
  798. public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) {
  799. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
  800. $this->loadArray($ret);
  801. }
  802. public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
  803. if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
  804. $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
  805. $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
  806. $ret = array();
  807. foreach ($allowed as $key) {
  808. list($ns, $directive) = $key;
  809. $skey = "$ns.$directive";
  810. if (!empty($array["Null_$skey"])) {
  811. $ret[$ns][$directive] = null;
  812. continue;
  813. }
  814. if (!isset($array[$skey])) continue;
  815. $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
  816. $ret[$ns][$directive] = $value;
  817. }
  818. return $ret;
  819. }
  820. public function loadIni($filename) {
  821. if ($this->isFinalized('Cannot load directives after finalization')) return;
  822. $array = parse_ini_file($filename, true);
  823. $this->loadArray($array);
  824. }
  825. public function isFinalized($error = false) {
  826. if ($this->finalized && $error) {
  827. trigger_error($error, E_USER_ERROR);
  828. }
  829. return $this->finalized;
  830. }
  831. public function autoFinalize() {
  832. if (!$this->finalized && $this->autoFinalize) $this->finalize();
  833. }
  834. public function finalize() {
  835. $this->finalized = true;
  836. }
  837. }
  838. abstract class HTMLPurifier_ConfigDef {
  839. public $class = false;
  840. }
  841. class HTMLPurifier_ConfigSchema {
  842. public $defaults = array();
  843. public $info = array();
  844. static protected $singleton;
  845. protected $parser;
  846. public function __construct() {
  847. $this->parser = new HTMLPurifier_VarParser_Flexible();
  848. }
  849. public static function makeFromSerial() {
  850. return unserialize(file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'));
  851. }
  852. public static function instance($prototype = null) {
  853. if ($prototype !== null) {
  854. HTMLPurifier_ConfigSchema::$singleton = $prototype;
  855. } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
  856. HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
  857. }
  858. return HTMLPurifier_ConfigSchema::$singleton;
  859. }
  860. public function add($namespace, $name, $default, $type, $allow_null) {
  861. $default = $this->parser->parse($default, $type, $allow_null);
  862. $this->info[$namespace][$name] = new HTMLPurifier_ConfigDef_Directive();
  863. $this->info[$namespace][$name]->type = $type;
  864. $this->info[$namespace][$name]->allow_null = $allow_null;
  865. $this->defaults[$namespace][$name] = $default;
  866. }
  867. public function addNamespace($namespace) {
  868. $this->info[$namespace] = array();
  869. $this->defaults[$namespace] = array();
  870. }
  871. public function addValueAliases($namespace, $name, $aliases) {
  872. foreach ($aliases as $alias => $real) {
  873. $this->info[$namespace][$name]->aliases[$alias] = $real;
  874. }
  875. }
  876. public function addAllowedValues($namespace, $name, $allowed) {
  877. $type = $this->info[$namespace][$name]->type;
  878. $this->info[$namespace][$name]->allowed = $allowed;
  879. }
  880. public function addAlias($namespace, $name, $new_namespace, $new_name) {
  881. $this->info[$namespace][$name] = new HTMLPurifier_ConfigDef_DirectiveAlias($new_namespace, $new_name);
  882. }
  883. /** @see HTMLPurifier_ConfigSchema->set() */
  884. public static function define($namespace, $name, $default, $type, $description) {
  885. HTMLPurifier_ConfigSchema::deprecated(__METHOD__);
  886. $type_values = explode('/', $type, 2);
  887. $type = $type_values[0];
  888. $modifier = isset($type_values[1]) ? $type_values[1] : false;
  889. $allow_null = ($modifier === 'null');
  890. $def = HTMLPurifier_ConfigSchema::instance();
  891. $def->add($namespace, $name, $default, $type, $allow_null);
  892. }
  893. /** @see HTMLPurifier_ConfigSchema->addNamespace() */
  894. public static function defineNamespace($namespace, $description) {
  895. HTMLPurifier_ConfigSchema::deprecated(__METHOD__);
  896. $def = HTMLPurifier_ConfigSchema::instance();
  897. $def->addNamespace($namespace);
  898. }
  899. /** @see HTMLPurifier_ConfigSchema->addValueAliases() */
  900. public static function defineValueAliases($namespace, $name, $aliases) {
  901. HTMLPurifier_ConfigSchema::deprecated(__METHOD__);
  902. $def = HTMLPurifier_ConfigSchema::instance();
  903. $def->addValueAliases($namespace, $name, $aliases);
  904. }
  905. /** @see HTMLPurifier_ConfigSchema->addAllowedValues() */
  906. public static function defineAllowedValues($namespace, $name, $allowed_values) {
  907. HTMLPurifier_ConfigSchema::deprecated(__METHOD__);
  908. $allowed = array();
  909. foreach ($allowed_values as $value) {
  910. $allowed[$value] = true;
  911. }
  912. $def = HTMLPurifier_ConfigSchema::instance();
  913. $def->addAllowedValues($namespace, $name, $allowed);
  914. }
  915. /** @see HTMLPurifier_ConfigSchema->addAlias() */
  916. public static function defineAlias($namespace, $name, $new_namespace, $new_name) {
  917. HTMLPurifier_ConfigSchema::deprecated(__METHOD__);
  918. $def = HTMLPurifier_ConfigSchema::instance();
  919. $def->addAlias($namespace, $name, $new_namespace, $new_name);
  920. }
  921. /** @deprecated, use HTMLPurifier_VarParser->parse() */
  922. public function validate($a, $b, $c = false) {
  923. trigger_error("HTMLPurifier_ConfigSchema->validate deprecated, use HTMLPurifier_VarParser->parse instead", E_USER_NOTICE);
  924. return $this->parser->parse($a, $b, $c);
  925. }
  926. private static function deprecated($method) {
  927. trigger_error("Static HTMLPurifier_ConfigSchema::$method deprecated, use add*() method instead", E_USER_NOTICE);
  928. }
  929. }
  930. class HTMLPurifier_ContentSets
  931. {
  932. public $info = array();
  933. public $lookup = array();
  934. protected $keys = array();
  935. protected $values = array();
  936. public function __construct($modules) {
  937. if (!is_array($modules)) $modules = array($modules);
  938. foreach ($modules as $module_i => $module) {
  939. foreach ($module->content_sets as $key => $value) {
  940. if (isset($this->info[$key])) {
  941. $this->info[$key] = $this->info[$key] . ' | ' . $value;
  942. } else {
  943. $this->info[$key] = $value;
  944. }
  945. }
  946. }
  947. $this->keys = array_keys($this->info);
  948. foreach ($this->info as $i => $set) {
  949. $this->info[$i] =
  950. str_replace(
  951. $this->keys,
  952. array_values($this->info),
  953. $set);
  954. }
  955. $this->values = array_values($this->info);
  956. foreach ($this->info as $name => $set) {
  957. $this->lookup[$name] = $this->convertToLookup($set);
  958. }
  959. }
  960. public function generateChildDef(&$def, $module) {
  961. if (!empty($def->child)) return;
  962. $content_model = $def->content_model;
  963. if (is_string($content_model)) {
  964. $def->content_model = str_replace(
  965. $this->keys, $this->values, $content_model);
  966. }
  967. $def->child = $this->getChildDef($def, $module);
  968. }
  969. public function getChildDef($def, $module) {
  970. $value = $def->content_model;
  971. if (is_object($value)) {
  972. trigger_error(
  973. 'Literal object child definitions should be stored in '.
  974. 'ElementDef->child not ElementDef->content_model',
  975. E_USER_NOTICE
  976. );
  977. return $value;
  978. }
  979. switch ($def->content_model_type) {
  980. case 'required':
  981. return new HTMLPurifier_ChildDef_Required($value);
  982. case 'optional':
  983. return new HTMLPurifier_ChildDef_Optional($value);
  984. case 'empty':
  985. return new HTMLPurifier_ChildDef_Empty();
  986. case 'custom':
  987. return new HTMLPurifier_ChildDef_Custom($value);
  988. }
  989. $return = false;
  990. if ($module->defines_child_def) {
  991. $return = $module->getChildDef($def);
  992. }
  993. if ($return !== false) return $return;
  994. trigger_error(
  995. 'Could not determine which ChildDef class to instantiate',
  996. E_USER_ERROR
  997. );
  998. return false;
  999. }
  1000. protected function convertToLookup($string) {
  1001. $array = explode('|', str_replace(' ', '', $string));
  1002. $ret = array();
  1003. foreach ($array as $i => $k) {
  1004. $ret[$k] = true;
  1005. }
  1006. return $ret;
  1007. }
  1008. }
  1009. class HTMLPurifier_Context
  1010. {
  1011. private $_storage = array();
  1012. public function register($name, &$ref) {
  1013. if (isset($this->_storage[$name])) {
  1014. trigger_error("Name $name produces collision, cannot re-register",
  1015. E_USER_ERROR);
  1016. return;
  1017. }
  1018. $this->_storage[$name] =& $ref;
  1019. }
  1020. public function &get($name, $ignore_error = false) {
  1021. if (!isset($this->_storage[$name])) {
  1022. if (!$ignore_error) {
  1023. trigger_error("Attempted to retrieve non-existent variable $name",
  1024. E_USER_ERROR);
  1025. }
  1026. $var = null;
  1027. return $var;
  1028. }
  1029. return $this->_storage[$name];
  1030. }
  1031. public function destroy($name) {
  1032. if (!isset($this->_storage[$name])) {
  1033. trigger_error("Attempted to destroy non-existent variable $name",
  1034. E_USER_ERROR);
  1035. return;
  1036. }
  1037. unset($this->_storage[$name]);
  1038. }
  1039. public function exists($name) {
  1040. return isset($this->_storage[$name]);
  1041. }
  1042. public function loadArray($context_array) {
  1043. foreach ($context_array as $key => $discard) {
  1044. $this->register($key, $context_array[$key]);
  1045. }
  1046. }
  1047. }
  1048. abstract class HTMLPurifier_DefinitionCache
  1049. {
  1050. public $type;
  1051. public function __construct($type) {
  1052. $this->type = $type;
  1053. }
  1054. public function generateKey($config) {
  1055. return $config->version . ',' .
  1056. $config->getBatchSerial($this->type) . ',' .
  1057. $config->get($this->type, 'DefinitionRev');
  1058. }
  1059. public function isOld($key, $config) {
  1060. if (substr_count($key, ',') < 2) return true;
  1061. list($version, $hash, $revision) = explode(',', $key, 3);
  1062. $compare = version_compare($version, $config->version);
  1063. if ($compare != 0) return true;
  1064. if (
  1065. $hash == $config->getBatchSerial($this->type) &&
  1066. $revision < $config->get($this->type, 'DefinitionRev')
  1067. ) return true;
  1068. return false;
  1069. }
  1070. public function checkDefType($def) {
  1071. if ($def->type !== $this->type) {
  1072. trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
  1073. return false;
  1074. }
  1075. return true;
  1076. }
  1077. abstract public function add($def, $config);
  1078. abstract public function set($def, $config);
  1079. abstract public function replace($def, $config);
  1080. abstract public function get($config);
  1081. abstract public function remove($config);
  1082. abstract public function flush($config);
  1083. abstract public function cleanup($config);
  1084. }
  1085. class HTMLPurifier_DefinitionCacheFactory
  1086. {
  1087. protected $caches = array('Serializer' => array());
  1088. protected $implementations = array();
  1089. protected $decorators = array();
  1090. public function setup() {
  1091. $this->addDecorator('Cleanup');
  1092. }
  1093. public static function instance($prototype = null) {
  1094. static $instance;
  1095. if ($prototype !== null) {
  1096. $instance = $prototype;
  1097. } elseif ($instance === null || $prototype === true) {
  1098. $instance = new HTMLPurifier_DefinitionCacheFactory();
  1099. $instance->setup();
  1100. }
  1101. return $instance;
  1102. }
  1103. public function register($short, $long) {
  1104. $this->implementations[$short] = $long;
  1105. }
  1106. public function create($type, $config) {
  1107. $method = $config->get('Cache', 'DefinitionImpl');
  1108. if ($method === null) {
  1109. return new HTMLPurifier_DefinitionCache_Null($type);
  1110. }
  1111. if (!empty($this->caches[$method][$type])) {
  1112. return $this->caches[$method][$type];
  1113. }
  1114. if (
  1115. isset($this->implementations[$method]) &&
  1116. class_exists($class = $this->implementations[$method], false)
  1117. ) {
  1118. $cache = new $class($type);
  1119. } else {
  1120. if ($method != 'Serializer') {
  1121. trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
  1122. }
  1123. $cache = new HTMLPurifier_DefinitionCache_Serializer($type);
  1124. }
  1125. foreach ($this->decorators as $decorator) {
  1126. $new_cache = $decorator->decorate($cache);
  1127. unset($cache);
  1128. $cache = $new_cache;
  1129. }
  1130. $this->caches[$method][$type] = $cache;
  1131. return $this->caches[$method][$type];
  1132. }
  1133. public function addDecorator($decorator) {
  1134. if (is_string($decorator)) {
  1135. $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
  1136. $decorator = new $class;
  1137. }
  1138. $this->decorators[$decorator->name] = $decorator;
  1139. }
  1140. }
  1141. class HTMLPurifier_Doctype
  1142. {
  1143. public $name;
  1144. public $modules = array();
  1145. public $tidyModules = array();
  1146. public $xml = true;
  1147. public $aliases = array();
  1148. public $dtdPublic;
  1149. public $dtdSystem;
  1150. public function __construct($name = null, $xml = true, $modules = array(),
  1151. $tidyModules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null
  1152. ) {
  1153. $this->name = $name;
  1154. $this->xml = $xml;
  1155. $this->modules = $modules;
  1156. $this->tidyModules = $tidyModules;
  1157. $this->aliases = $aliases;
  1158. $this->dtdPublic = $dtd_public;
  1159. $this->dtdSystem = $dtd_system;
  1160. }
  1161. }
  1162. class HTMLPurifier_DoctypeRegistry
  1163. {
  1164. protected $doctypes;
  1165. protected $aliases;
  1166. public function register($doctype, $xml = true, $modules = array(),
  1167. $tidy_modules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null
  1168. ) {
  1169. if (!is_array($modules)) $modules = array($modules);
  1170. if (!is_array($tidy_modules)) $tidy_modules = array($tidy_modules);
  1171. if (!is_array($aliases)) $aliases = array($aliases);
  1172. if (!is_object($doctype)) {
  1173. $doctype = new HTMLPurifier_Doctype(
  1174. $doctype, $xml, $modules, $tidy_modules, $aliases, $dtd_public, $dtd_system
  1175. );
  1176. }
  1177. $this->doctypes[$doctype->name] = $doctype;
  1178. $name = $doctype->name;
  1179. foreach ($doctype->aliases as $alias) {
  1180. if (isset($this->doctypes[$alias])) continue;
  1181. $this->aliases[$alias] = $name;
  1182. }
  1183. if (isset($this->aliases[$name])) unset($this->aliases[$name]);
  1184. return $doctype;
  1185. }
  1186. public function get($doctype) {
  1187. if (isset($this->aliases[$doctype])) $doctype = $this->aliases[$doctype];
  1188. if (!isset($this->doctypes[$doctype])) {
  1189. trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
  1190. $anon = new HTMLPurifier_Doctype($doctype);
  1191. return $anon;
  1192. }
  1193. return $this->doctypes[$doctype];
  1194. }
  1195. public function make($config) {
  1196. return clone $this->get($this->getDoctypeFromConfig($config));
  1197. }
  1198. public function getDoctypeFromConfig($config) {
  1199. $doctype = $config->get('HTML', 'Doctype');
  1200. if (!empty($doctype)) return $doctype;
  1201. $doctype = $config->get('HTML', 'CustomDoctype');
  1202. if (!empty($doctype)) return $doctype;
  1203. if ($config->get('HTML', 'XHTML')) {
  1204. $doctype = 'XHTML 1.0';
  1205. } else {
  1206. $doctype = 'HTML 4.01';
  1207. }
  1208. if ($config->get('HTML', 'Strict')) {
  1209. $doctype .= ' Strict';
  1210. } else {
  1211. $doctype .= ' Transitional';
  1212. }
  1213. return $doctype;
  1214. }
  1215. }
  1216. class HTMLPurifier_ElementDef
  1217. {
  1218. public $standalone = true;
  1219. public $attr = array();
  1220. public $attr_transform_pre = array();
  1221. public $attr_transform_post = array();
  1222. public $child;
  1223. public $content_model;
  1224. public $content_model_type;
  1225. public $descendants_are_inline = false;
  1226. public $required_attr = array();
  1227. public $excludes = array();
  1228. public static function create($content_model, $content_model_type, $attr) {
  1229. $def = new HTMLPurifier_ElementDef();
  1230. $def->content_model = $content_model;
  1231. $def->content_model_type = $content_model_type;
  1232. $def->attr = $attr;
  1233. return $def;
  1234. }
  1235. public function mergeIn($def) {
  1236. foreach($def->attr as $k => $v) {
  1237. if ($k === 0) {
  1238. foreach ($v as $v2) {
  1239. $this->attr[0][] = $v2;
  1240. }
  1241. continue;
  1242. }
  1243. if ($v === false) {
  1244. if (isset($this->attr[$k])) unset($this->attr[$k]);
  1245. continue;
  1246. }
  1247. $this->attr[$k] = $v;
  1248. }
  1249. $this->_mergeAssocArray($this->attr_transform_pre, $def->attr_transform_pre);
  1250. $this->_mergeAssocArray($this->attr_transform_post, $def->attr_transform_post);
  1251. $this->_mergeAssocArray($this->excludes, $def->excludes);
  1252. if(!empty($def->content_model)) {
  1253. $this->content_model .= ' | ' . $def->content_model;
  1254. $this->child = false;
  1255. }
  1256. if(!empty($def->content_model_type)) {
  1257. $this->content_model_type = $def->content_model_type;
  1258. $this->child = false;
  1259. }
  1260. if(!is_null($def->child)) $this->child = $def->child;
  1261. if($def->descendants_are_inline) $this->descendants_are_inline = $def->descendants_are_inline;
  1262. }
  1263. private function _mergeAssocArray(&$a1, $a2) {
  1264. foreach ($a2 as $k => $v) {
  1265. if ($v === false) {
  1266. if (isset($a1[$k])) unset($a1[$k]);
  1267. continue;
  1268. }
  1269. $a1[$k] = $v;
  1270. }
  1271. }
  1272. }
  1273. class HTMLPurifier_Encoder
  1274. {
  1275. private function __construct() {
  1276. trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
  1277. }
  1278. private static function muteErrorHandler() {}
  1279. public static function cleanUTF8($str, $force_php = false) {
  1280. static $non_sgml_chars = array();
  1281. if (empty($non_sgml_chars)) {
  1282. for ($i = 0; $i <= 31; $i++) {
  1283. if ($i == 9 || $i == 13 || $i == 10) continue;
  1284. $non_sgml_chars[chr($i)] = '';
  1285. }
  1286. for ($i = 127; $i <= 159; $i++) {
  1287. $non_sgml_chars[HTMLPurifier_Encoder::unichr($i)] = '';
  1288. }
  1289. }
  1290. static $iconv = null;
  1291. if ($iconv === null) $iconv = function_exists('iconv');
  1292. if (preg_match('/^.{1}/us', $str)) {
  1293. return strtr($str, $non_sgml_chars);
  1294. }
  1295. if ($iconv && !$force_php) {
  1296. set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
  1297. $str = iconv('UTF-8', 'UTF-8//IGNORE', $str);
  1298. restore_error_handler();
  1299. return strtr($str, $non_sgml_chars);
  1300. }
  1301. $mState = 0;
  1302. $mUcs4 = 0;
  1303. $mBytes = 1;
  1304. $out = '';
  1305. $char = '';
  1306. $len = strlen($str);
  1307. for($i = 0; $i < $len; $i++) {
  1308. $in = ord($str{$i});
  1309. $char .= $str[$i];
  1310. if (0 == $mState) {
  1311. if (0 == (0x80 & ($in))) {
  1312. if (($in <= 31 || $in == 127) &&
  1313. !($in == 9 || $in == 13 || $in == 10)
  1314. ) {
  1315. } else {
  1316. $out .= $char;
  1317. }
  1318. $char = '';
  1319. $mBytes = 1;
  1320. } elseif (0xC0 == (0xE0 & ($in))) {
  1321. $mUcs4 = ($in);
  1322. $mUcs4 = ($mUcs4 & 0x1F) << 6;
  1323. $mState = 1;
  1324. $mBytes = 2;
  1325. } elseif (0xE0 == (0xF0 & ($in))) {
  1326. $mUcs4 = ($in);
  1327. $mUcs4 = ($mUcs4 & 0x0F) << 12;
  1328. $mState = 2;
  1329. $mBytes = 3;
  1330. } elseif (0xF0 == (0xF8 & ($in))) {
  1331. $mUcs4 = ($in);
  1332. $mUcs4 = ($mUcs4 & 0x07) << 18;
  1333. $mState = 3;
  1334. $mBytes = 4;
  1335. } elseif (0xF8 == (0xFC & ($in))) {
  1336. $mUcs4 = ($in);
  1337. $mUcs4 = ($mUcs4 & 0x03) << 24;
  1338. $mState = 4;
  1339. $mBytes = 5;
  1340. } elseif (0xFC == (0xFE & ($in))) {
  1341. $mUcs4 = ($in);
  1342. $mUcs4 = ($mUcs4 & 1) << 30;
  1343. $mState = 5;
  1344. $mBytes = 6;
  1345. } else {
  1346. $mState = 0;
  1347. $mUcs4 = 0;
  1348. $mBytes = 1;
  1349. $char = '';
  1350. }
  1351. } else {
  1352. if (0x80 == (0xC0 & ($in))) {
  1353. $shift = ($mState - 1) * 6;
  1354. $tmp = $in;
  1355. $tmp = ($tmp & 0x0000003F) << $shift;
  1356. $mUcs4 |= $tmp;
  1357. if (0 == --$mState) {
  1358. if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
  1359. ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
  1360. ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
  1361. (4 < $mBytes) ||
  1362. (($mUcs4 & 0xFFFFF800) == 0xD800) ||
  1363. ($mUcs4 > 0x10FFFF)
  1364. ) {
  1365. } elseif (0xFEFF != $mUcs4 &&
  1366. !($mUcs4 >= 128 && $mUcs4 <= 159)
  1367. ) {
  1368. $out .= $char;
  1369. }
  1370. $mState = 0;
  1371. $mUcs4 = 0;
  1372. $mBytes = 1;
  1373. $char = '';
  1374. }
  1375. } else {
  1376. $mState = 0;
  1377. $mUcs4 = 0;
  1378. $mBytes = 1;
  1379. $char ='';
  1380. }
  1381. }
  1382. }
  1383. return $out;
  1384. }
  1385. public static function unichr($code) {
  1386. if($code > 1114111 or $code < 0 or
  1387. ($code >= 55296 and $code <= 57343) ) {
  1388. return '';
  1389. }
  1390. $x = $y = $z = $w = 0;
  1391. if ($code < 128) {
  1392. $x = $code;
  1393. } else {
  1394. $x = ($code & 63) | 128;
  1395. if ($code < 2048) {
  1396. $y = (($code & 2047) >> 6) | 192;
  1397. } else {
  1398. $y = (($code & 4032) >> 6) | 128;
  1399. if($code < 65536) {
  1400. $z = (($code >> 12) & 15) | 224;
  1401. } else {
  1402. $z = (($code >> 12) & 63) | 128;
  1403. $w = (($code >> 18) & 7) | 240;
  1404. }
  1405. }
  1406. }
  1407. $ret = '';
  1408. if($w) $ret .= chr($w);
  1409. if($z) $ret .= chr($z);
  1410. if($y) $ret .= chr($y);
  1411. $ret .= chr($x);
  1412. return $ret;
  1413. }
  1414. public static function convertToUTF8($str, $config, $context) {
  1415. static $iconv = null;
  1416. if ($iconv === null) $iconv = function_exists('iconv');
  1417. $encoding = $config->get('Core', 'Encoding');
  1418. if ($encoding === 'utf-8') return $str;
  1419. if ($iconv && !$config->get('Test', 'ForceNoIconv')) {
  1420. set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
  1421. $str = iconv($encoding, 'utf-8//IGNORE', $str);
  1422. restore_error_handler();
  1423. return $str;
  1424. } elseif ($encoding === 'iso-8859-1') {
  1425. set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
  1426. $str = utf8_encode($str);
  1427. restore_error_handler();
  1428. return $str;
  1429. }
  1430. trigger_error('Encoding not supported', E_USER_ERROR);
  1431. }
  1432. public static function convertFromUTF8($str, $config, $context) {
  1433. static $iconv = null;
  1434. if ($iconv === null) $iconv = function_exists('iconv');
  1435. $encoding = $config->get('Core', 'Encoding');
  1436. if ($encoding === 'utf-8') return $str;
  1437. if ($config->get('Core', 'EscapeNonASCIICharacters')) {
  1438. $str = HTMLPurifier_Encoder::convertToASCIIDumbLossless($str);
  1439. }
  1440. if ($iconv && !$config->get('Test', 'ForceNoIconv')) {
  1441. set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
  1442. $str = iconv('utf-8', $encoding . '//IGNORE', $str);
  1443. restore_error_handler();
  1444. return $str;
  1445. } elseif ($encoding === 'iso-8859-1') {
  1446. set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
  1447. $str = utf8_decode($str);
  1448. restore_error_handler();
  1449. return $str;
  1450. }
  1451. trigger_error('Encoding not supported', E_USER_ERROR);
  1452. }
  1453. public static function convertToASCIIDumbLossless($str) {
  1454. $bytesleft = 0;
  1455. $result = '';
  1456. $working = 0;
  1457. $len = strlen($str);
  1458. for( $i = 0; $i < $len; $i++ ) {
  1459. $bytevalue = ord( $str[$i] );
  1460. if( $bytevalue <= 0x7F ) {
  1461. $result .= chr( $bytevalue );
  1462. $bytesleft = 0;
  1463. } elseif( $bytevalue <= 0xBF ) {
  1464. $working = $working << 6;
  1465. $working += ($bytevalue & 0x3F);
  1466. $bytesleft--;
  1467. if( $bytesleft <= 0 ) {
  1468. $result .= "&#" . $working . ";";
  1469. }
  1470. } elseif( $bytevalue <= 0xDF ) {
  1471. $working = $bytevalue & 0x1F;
  1472. $bytesleft = 1;
  1473. } elseif( $bytevalue <= 0xEF ) {
  1474. $working = $bytevalue & 0x0F;
  1475. $bytesleft = 2;
  1476. } else {
  1477. $working = $bytevalue & 0x07;
  1478. $bytesleft = 3;
  1479. }
  1480. }
  1481. return $result;
  1482. }
  1483. }
  1484. class HTMLPurifier_EntityLookup {
  1485. public $table;
  1486. public function setup($file = false) {
  1487. if (!$file) {
  1488. $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
  1489. }
  1490. $this->table = unserialize(file_get_contents($file));
  1491. }
  1492. public static function instance($prototype = false) {
  1493. static $instance = null;
  1494. if ($prototype) {
  1495. $instance = $prototype;
  1496. } elseif (!$instance) {
  1497. $instance = new HTMLPurifier_EntityLookup();
  1498. $instance->setup();
  1499. }
  1500. return $instance;
  1501. }
  1502. }
  1503. class HTMLPurifier_EntityParser
  1504. {
  1505. protected $_entity_lookup;
  1506. protected $_substituteEntitiesRegex =
  1507. '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
  1508. protected $_special_dec2str =
  1509. array(
  1510. 34 => '"',
  1511. 38 => '&',
  1512. 39 => "'",
  1513. 60 => '<',
  1514. 62 => '>'
  1515. );
  1516. protected $_special_ent2dec =
  1517. array(
  1518. 'quot' => 34,
  1519. 'amp' => 38,
  1520. 'lt' => 60,
  1521. 'gt' => 62
  1522. );
  1523. public function substituteNonSpecialEntities($string) {
  1524. return preg_replace_callback(
  1525. $this->_substituteEntitiesRegex,
  1526. array($this, 'nonSpecialEntityCallback'),
  1527. $string
  1528. );
  1529. }
  1530. protected function nonSpecialEntityCallback($matches) {
  1531. $entity = $matches[0];
  1532. $is_num = (@$matches[0][1] === '#');
  1533. if ($is_num) {
  1534. $is_hex = (@$entity[2] === 'x');
  1535. $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
  1536. if (isset($this->_special_dec2str[$code])) return $entity;
  1537. return HTMLPurifier_Encoder::unichr($code);
  1538. } else {
  1539. if (isset($this->_special_ent2dec[$matches[3]])) return $entity;
  1540. if (!$this->_entity_lookup) {
  1541. $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
  1542. }
  1543. if (isset($this->_entity_lookup->table[$matches[3]])) {
  1544. return $this->_entity_lookup->table[$matches[3]];
  1545. } else {
  1546. return $entity;
  1547. }
  1548. }
  1549. }
  1550. public function substituteSpecialEntities($string) {
  1551. return preg_replace_callback(
  1552. $this->_substituteEntitiesRegex,
  1553. array($this, 'specialEntityCallback'),
  1554. $string);
  1555. }
  1556. protected function specialEntityCallback($matches) {
  1557. $entity = $matches[0];
  1558. $is_num = (@$matches[0][1] === '#');
  1559. if ($is_num) {
  1560. $is_hex = (@$entity[2] === 'x');
  1561. $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
  1562. return isset($this->_special_dec2str[$int]) ?
  1563. $this->_special_dec2str[$int] :
  1564. $entity;
  1565. } else {
  1566. return isset($this->_special_ent2dec[$matches[3]]) ?
  1567. $this->_special_ent2dec[$matches[3]] :
  1568. $entity;
  1569. }
  1570. }
  1571. }
  1572. class HTMLPurifier_ErrorCollector
  1573. {
  1574. protected $errors = array();
  1575. protected $locale;
  1576. protected $generator;
  1577. protected $context;
  1578. public function __construct($context) {
  1579. $this->locale =& $context->get('Locale');
  1580. $this->generator =& $context->get('Generator');
  1581. $this->context = $context;
  1582. }
  1583. public function send($severity, $msg) {
  1584. $args = array();
  1585. if (func_num_args() > 2) {
  1586. $args = func_get_args();
  1587. array_shift($args);
  1588. unset($args[0]);
  1589. }
  1590. $token = $this->context->get('CurrentToken', true);
  1591. $line = $token ? $token->line : $this->context->get('CurrentLine', true);
  1592. $attr = $this->context->get('CurrentAttr', true);
  1593. $subst = array();
  1594. if (!is_null($token)) {
  1595. $args['CurrentToken'] = $token;
  1596. }
  1597. if (!is_null($attr)) {
  1598. $subst['$CurrentAttr.Name'] = $attr;
  1599. if (isset($token->attr[$attr])) $subst['$CurrentAttr.Value'] = $token->attr[$attr];
  1600. }
  1601. if (empty($args)) {
  1602. $msg = $this->locale->getMessage($msg);
  1603. } else {
  1604. $msg = $this->locale->formatMessage($msg, $args);
  1605. }
  1606. if (!empty($subst)) $msg = strtr($msg, $subst);
  1607. $this->errors[] = array($line, $severity, $msg);
  1608. }
  1609. public function getRaw() {
  1610. return $this->errors;
  1611. }
  1612. public function getHTMLFormatted($config) {
  1613. $ret = array();
  1614. $errors = $this->errors;
  1615. if ($config->get('Core', 'MaintainLineNumbers') !== false) {
  1616. $has_line = array();
  1617. $lines = array();
  1618. $original_order = array();
  1619. foreach ($errors as $i => $error) {
  1620. $has_line[] = (int) (bool) $error[0];
  1621. $lines[] = $error[0];
  1622. $original_order[] = $i;
  1623. }
  1624. array_multisort($has_line, SORT_DESC, $lines, SORT_ASC, $original_order, SORT_ASC, $errors);
  1625. }
  1626. foreach ($errors as $error) {
  1627. list($line, $severity, $msg) = $error;
  1628. $string = '';
  1629. $string .= '<strong>' . $this->locale->getErrorName($severity) . '</strong>: ';
  1630. $string .= $this->generator->escape($msg);
  1631. if ($line) {
  1632. $string .= $this->locale->formatMessage(
  1633. 'ErrorCollector: At line', array('line' => $line));
  1634. }
  1635. $ret[] = $string;
  1636. }
  1637. if (empty($errors)) {
  1638. return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
  1639. } else {
  1640. return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
  1641. }
  1642. }
  1643. }
  1644. class HTMLPurifier_Exception extends Exception
  1645. {
  1646. }
  1647. class HTMLPurifier_Filter
  1648. {
  1649. public $name;
  1650. public function preFilter($html, $config, $context) {
  1651. return $html;
  1652. }
  1653. public function postFilter($html, $config, $context) {
  1654. return $html;
  1655. }
  1656. }
  1657. class HTMLPurifier_Generator
  1658. {
  1659. private $_xhtml = true;
  1660. private $_scriptFix = false;
  1661. private $_def;
  1662. protected $config;
  1663. public function __construct($config = null, $context = null) {
  1664. if (!$config) $config = HTMLPurifier_Config::createDefault();
  1665. $this->config = $config;
  1666. $this->_scriptFix = $config->get('Output', 'CommentScriptContents');
  1667. $this->_def = $config->getHTMLDefinition();
  1668. $this->_xhtml = $this->_def->doctype->xml;
  1669. }
  1670. public function generateFromTokens($tokens) {
  1671. if (!$tokens) return '';
  1672. $html = '';
  1673. for ($i = 0, $size = count($tokens); $i < $size; $i++) {
  1674. if ($this->_scriptFix && $tokens[$i]->name === 'script'
  1675. && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
  1676. $html .= $this->generateFromToken($tokens[$i++]);
  1677. $html .= $this->generateScriptFromToken($tokens[$i++]);
  1678. }
  1679. $html .= $this->generateFromToken($tokens[$i]);
  1680. }
  1681. if (extension_loaded('tidy') && $this->config->get('Output', 'TidyFormat')) {
  1682. $tidy = new Tidy;
  1683. $tidy->parseString($html, array(
  1684. 'indent'=> true,
  1685. 'output-xhtml' => $this->_xhtml,
  1686. 'show-body-only' => true,
  1687. 'indent-spaces' => 2,
  1688. 'wrap' => 68,
  1689. ), 'utf8');
  1690. $tidy->cleanRepair();
  1691. $html = (string) $tidy;
  1692. }
  1693. $nl = $this->config->get('Output', 'Newline');
  1694. if ($nl === null) $nl = PHP_EOL;
  1695. if ($nl !== "\n") $html = str_replace("\n", $nl, $html);
  1696. return $html;
  1697. }
  1698. public function generateFromToken($token) {
  1699. if (!$token instanceof HTMLPurifier_Token) {
  1700. trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
  1701. return '';
  1702. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  1703. $attr = $this->generateAttributes($token->attr, $token->name);
  1704. return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
  1705. } elseif ($token instanceof HTMLPurifier_Token_End) {
  1706. return '</' . $token->name . '>';
  1707. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  1708. $attr = $this->generateAttributes($token->attr, $token->name);
  1709. return '<' . $token->name . ($attr ? ' ' : '') . $attr .
  1710. ( $this->_xhtml ? ' /': '' )
  1711. . '>';
  1712. } elseif ($token instanceof HTMLPurifier_Token_Text) {
  1713. return $this->escape($token->data, ENT_NOQUOTES);
  1714. } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  1715. return '<!--' . $token->data . '-->';
  1716. } else {
  1717. return '';
  1718. }
  1719. }
  1720. public function generateScriptFromToken($token) {
  1721. if (!$token instanceof HTMLPurifier_Token_Text) return $this->generateFromToken($token);
  1722. $data = preg_replace('#//\s*$#', '', $token->data);
  1723. return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
  1724. }
  1725. public function generateAttributes($assoc_array_of_attributes, $element = false) {
  1726. $html = '';
  1727. foreach ($assoc_array_of_attributes as $key => $value) {
  1728. if (!$this->_xhtml) {
  1729. if (strpos($key, ':') !== false) continue;
  1730. if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
  1731. $html .= $key . ' ';
  1732. continue;
  1733. }
  1734. }
  1735. $html .= $key.'="'.$this->escape($value).'" ';
  1736. }
  1737. return rtrim($html);
  1738. }
  1739. public function escape($string, $quote = ENT_COMPAT) {
  1740. return htmlspecialchars($string, $quote, 'UTF-8');
  1741. }
  1742. }
  1743. class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
  1744. {
  1745. public $info = array();
  1746. public $info_global_attr = array();
  1747. public $info_parent = 'div';
  1748. public $info_parent_def;
  1749. public $info_block_wrapper = 'p';
  1750. public $info_tag_transform = array();
  1751. public $info_attr_transform_pre = array();
  1752. public $info_attr_transform_post = array();
  1753. public $info_content_sets = array();
  1754. public $doctype;
  1755. public function addAttribute($element_name, $attr_name, $def) {
  1756. $module = $this->getAnonymousModule();
  1757. if (!isset($module->info[$element_name])) {
  1758. $element = $module->addBlankElement($element_name);
  1759. } else {
  1760. $element = $module->info[$element_name];
  1761. }
  1762. $element->attr[$attr_name] = $def;
  1763. }
  1764. public function addElement($element_name, $type, $contents, $attr_collections, $attributes) {
  1765. $module = $this->getAnonymousModule();
  1766. $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
  1767. return $element;
  1768. }
  1769. public function addBlankElement($element_name) {
  1770. $module = $this->getAnonymousModule();
  1771. $element = $module->addBlankElement($element_name);
  1772. return $element;
  1773. }
  1774. public function getAnonymousModule() {
  1775. if (!$this->_anonModule) {
  1776. $this->_anonModule = new HTMLPurifier_HTMLModule();
  1777. $this->_anonModule->name = 'Anonymous';
  1778. }
  1779. return $this->_anonModule;
  1780. }
  1781. private $_anonModule;
  1782. public $type = 'HTML';
  1783. public $manager; /**< Instance of HTMLPurifier_HTMLModuleManager */
  1784. public function __construct() {
  1785. $this->manager = new HTMLPurifier_HTMLModuleManager();
  1786. }
  1787. protected function doSetup($config) {
  1788. $this->processModules($config);
  1789. $this->setupConfigStuff($config);
  1790. unset($this->manager);
  1791. foreach ($this->info as $k => $v) {
  1792. unset($this->info[$k]->content_model);
  1793. unset($this->info[$k]->content_model_type);
  1794. }
  1795. }
  1796. protected function processModules($config) {
  1797. if ($this->_anonModule) {
  1798. $this->manager->addModule($this->_anonModule);
  1799. unset($this->_anonModule);
  1800. }
  1801. $this->manager->setup($config);
  1802. $this->doctype = $this->manager->doctype;
  1803. foreach ($this->manager->modules as $module) {
  1804. foreach($module->info_tag_transform as $k => $v) {
  1805. if ($v === false) unset($this->info_tag_transform[$k]);
  1806. else $this->info_tag_transform[$k] = $v;
  1807. }
  1808. foreach($module->info_attr_transform_pre as $k => $v) {
  1809. if ($v === false) unset($this->info_attr_transform_pre[$k]);
  1810. else $this->info_attr_transform_pre[$k] = $v;
  1811. }
  1812. foreach($module->info_attr_transform_post as $k => $v) {
  1813. if ($v === false) unset($this->info_attr_transform_post[$k]);
  1814. else $this->info_attr_transform_post[$k] = $v;
  1815. }
  1816. }
  1817. $this->info = $this->manager->getElements();
  1818. $this->info_content_sets = $this->manager->contentSets->lookup;
  1819. }
  1820. protected function setupConfigStuff($config) {
  1821. $block_wrapper = $config->get('HTML', 'BlockWrapper');
  1822. if (isset($this->info_content_sets['Block'][$block_wrapper])) {
  1823. $this->info_block_wrapper = $block_wrapper;
  1824. } else {
  1825. trigger_error('Cannot use non-block element as block wrapper',
  1826. E_USER_ERROR);
  1827. }
  1828. $parent = $config->get('HTML', 'Parent');
  1829. $def = $this->manager->getElement($parent, true);
  1830. if ($def) {
  1831. $this->info_parent = $parent;
  1832. $this->info_parent_def = $def;
  1833. } else {
  1834. trigger_error('Cannot use unrecognized element as parent',
  1835. E_USER_ERROR);
  1836. $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
  1837. }
  1838. $support = "(for information on implementing this, see the ".
  1839. "support forums) ";
  1840. $allowed_elements = $config->get('HTML', 'AllowedElements');
  1841. $allowed_attributes = $config->get('HTML', 'AllowedAttributes');
  1842. if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
  1843. $allowed = $config->get('HTML', 'Allowed');
  1844. if (is_string($allowed)) {
  1845. list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
  1846. }
  1847. }
  1848. if (is_array($allowed_elements)) {
  1849. foreach ($this->info as $name => $d) {
  1850. if(!isset($allowed_elements[$name])) unset($this->info[$name]);
  1851. unset($allowed_elements[$name]);
  1852. }
  1853. foreach ($allowed_elements as $element => $d) {
  1854. $element = htmlspecialchars($element);
  1855. trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
  1856. }
  1857. }
  1858. $allowed_attributes_mutable = $allowed_attributes;
  1859. if (is_array($allowed_attributes)) {
  1860. foreach ($this->info_global_attr as $attr => $x) {
  1861. $keys = array($attr, "*@$attr", "*.$attr");
  1862. $delete = true;
  1863. foreach ($keys as $key) {
  1864. if ($delete && isset($allowed_attributes[$key])) {
  1865. $delete = false;
  1866. }
  1867. if (isset($allowed_attributes_mutable[$key])) {
  1868. unset($allowed_attributes_mutable[$key]);
  1869. }
  1870. }
  1871. if ($delete) unset($this->info_global_attr[$attr]);
  1872. }
  1873. foreach ($this->info as $tag => $info) {
  1874. foreach ($info->attr as $attr => $x) {
  1875. $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
  1876. $delete = true;
  1877. foreach ($keys as $key) {
  1878. if ($delete && isset($allowed_attributes[$key])) {
  1879. $delete = false;
  1880. }
  1881. if (isset($allowed_attributes_mutable[$key])) {
  1882. unset($allowed_attributes_mutable[$key]);
  1883. }
  1884. }
  1885. if ($delete) unset($this->info[$tag]->attr[$attr]);
  1886. }
  1887. }
  1888. foreach ($allowed_attributes_mutable as $elattr => $d) {
  1889. $bits = preg_split('/[.@]/', $elattr, 2);
  1890. $c = count($bits);
  1891. switch ($c) {
  1892. case 2:
  1893. if ($bits[0] !== '*') {
  1894. $element = htmlspecialchars($bits[0]);
  1895. $attribute = htmlspecialchars($bits[1]);
  1896. if (!isset($this->info[$element])) {
  1897. trigger_error("Cannot allow attribute '$attribute' if element '$element' is not allowed/supported $support");
  1898. } else {
  1899. trigger_error("Attribute '$attribute' in element '$element' not supported $support",
  1900. E_USER_WARNING);
  1901. }
  1902. break;
  1903. }
  1904. case 1:
  1905. $attribute = htmlspecialchars($bits[0]);
  1906. trigger_error("Global attribute '$attribute' is not ".
  1907. "supported in any elements $support",
  1908. E_USER_WARNING);
  1909. break;
  1910. }
  1911. }
  1912. }
  1913. $forbidden_elements = $config->get('HTML', 'ForbiddenElements');
  1914. $forbidden_attributes = $config->get('HTML', 'ForbiddenAttributes');
  1915. foreach ($this->info as $tag => $info) {
  1916. if (isset($forbidden_elements[$tag])) {
  1917. unset($this->info[$tag]);
  1918. continue;
  1919. }
  1920. foreach ($info->attr as $attr => $x) {
  1921. if (
  1922. isset($forbidden_attributes["$tag@$attr"]) ||
  1923. isset($forbidden_attributes["*@$attr"]) ||
  1924. isset($forbidden_attributes[$attr])
  1925. ) {
  1926. unset($this->info[$tag]->attr[$attr]);
  1927. continue;
  1928. }
  1929. elseif (isset($forbidden_attributes["$tag.$attr"])) {
  1930. trigger_error("Error with $tag.$attr: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead", E_USER_WARNING);
  1931. }
  1932. }
  1933. }
  1934. foreach ($forbidden_attributes as $key => $v) {
  1935. if (strlen($key) < 2) continue;
  1936. if ($key[0] != '*') continue;
  1937. if ($key[1] == '.') {
  1938. trigger_error("Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", E_USER_WARNING);
  1939. }
  1940. }
  1941. }
  1942. public function parseTinyMCEAllowedList($list) {
  1943. $list = str_replace(array(' ', "\t"), '', $list);
  1944. $elements = array();
  1945. $attributes = array();
  1946. $chunks = preg_split('/(,|[\n\r]+)/', $list);
  1947. foreach ($chunks as $chunk) {
  1948. if (empty($chunk)) continue;
  1949. if (!strpos($chunk, '[')) {
  1950. $element = $chunk;
  1951. $attr = false;
  1952. } else {
  1953. list($element, $attr) = explode('[', $chunk);
  1954. }
  1955. if ($element !== '*') $elements[$element] = true;
  1956. if (!$attr) continue;
  1957. $attr = substr($attr, 0, strlen($attr) - 1);
  1958. $attr = explode('|', $attr);
  1959. foreach ($attr as $key) {
  1960. $attributes["$element.$key"] = true;
  1961. }
  1962. }
  1963. return array($elements, $attributes);
  1964. }
  1965. }
  1966. class HTMLPurifier_HTMLModule
  1967. {
  1968. public $name;
  1969. public $elements = array();
  1970. public $info = array();
  1971. public $content_sets = array();
  1972. public $attr_collections = array();
  1973. public $info_tag_transform = array();
  1974. public $info_attr_transform_pre = array();
  1975. public $info_attr_transform_post = array();
  1976. public $defines_child_def = false;
  1977. public $safe = true;
  1978. public function getChildDef($def) {return false;}
  1979. public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()) {
  1980. $this->elements[] = $element;
  1981. list($content_model_type, $content_model) = $this->parseContents($contents);
  1982. $this->mergeInAttrIncludes($attr, $attr_includes);
  1983. if ($type) $this->addElementToContentSet($element, $type);
  1984. $this->info[$element] = HTMLPurifier_ElementDef::create(
  1985. $content_model, $content_model_type, $attr
  1986. );
  1987. if (!is_string($contents)) $this->info[$element]->child = $contents;
  1988. return $this->info[$element];
  1989. }
  1990. public function addBlankElement($element) {
  1991. if (!isset($this->info[$element])) {
  1992. $this->elements[] = $element;
  1993. $this->info[$element] = new HTMLPurifier_ElementDef();
  1994. $this->info[$element]->standalone = false;
  1995. } else {
  1996. trigger_error("Definition for $element already exists in module, cannot redefine");
  1997. }
  1998. return $this->info[$element];
  1999. }
  2000. public function addElementToContentSet($element, $type) {
  2001. if (!isset($this->content_sets[$type])) $this->content_sets[$type] = '';
  2002. else $this->content_sets[$type] .= ' | ';
  2003. $this->content_sets[$type] .= $element;
  2004. }
  2005. public function parseContents($contents) {
  2006. if (!is_string($contents)) return array(null, null);
  2007. switch ($contents) {
  2008. case 'Empty':
  2009. return array('empty', '');
  2010. case 'Inline':
  2011. return array('optional', 'Inline | #PCDATA');
  2012. case 'Flow':
  2013. return array('optional', 'Flow | #PCDATA');
  2014. }
  2015. list($content_model_type, $content_model) = explode(':', $contents);
  2016. $content_model_type = strtolower(trim($content_model_type));
  2017. $content_model = trim($content_model);
  2018. return array($content_model_type, $content_model);
  2019. }
  2020. public function mergeInAttrIncludes(&$attr, $attr_includes) {
  2021. if (!is_array($attr_includes)) {
  2022. if (empty($attr_includes)) $attr_includes = array();
  2023. else $attr_includes = array($attr_includes);
  2024. }
  2025. $attr[0] = $attr_includes;
  2026. }
  2027. public function makeLookup($list) {
  2028. if (is_string($list)) $list = func_get_args();
  2029. $ret = array();
  2030. foreach ($list as $value) {
  2031. if (is_null($value)) continue;
  2032. $ret[$value] = true;
  2033. }
  2034. return $ret;
  2035. }
  2036. }
  2037. class HTMLPurifier_HTMLModuleManager
  2038. {
  2039. public $doctypes;
  2040. public $doctype;
  2041. public $attrTypes;
  2042. public $modules = array();
  2043. public $registeredModules = array();
  2044. public $userModules = array();
  2045. public $elementLookup = array();
  2046. /** List of prefixes we should use for registering small names */
  2047. public $prefixes = array('HTMLPurifier_HTMLModule_');
  2048. public $contentSets; /**< Instance of HTMLPurifier_ContentSets */
  2049. public $attrCollections; /**< Instance of HTMLPurifier_AttrCollections */
  2050. /** If set to true, unsafe elements and attributes will be allowed */
  2051. public $trusted = false;
  2052. public function __construct() {
  2053. $this->attrTypes = new HTMLPurifier_AttrTypes();
  2054. $this->doctypes = new HTMLPurifier_DoctypeRegistry();
  2055. $common = array(
  2056. 'CommonAttributes', 'Text', 'Hypertext', 'List',
  2057. 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
  2058. 'StyleAttribute', 'Scripting', 'Object'
  2059. );
  2060. $transitional = array('Legacy', 'Target');
  2061. $xml = array('XMLCommonAttributes');
  2062. $non_xml = array('NonXMLCommonAttributes');
  2063. $this->doctypes->register(
  2064. 'HTML 4.01 Transitional', false,
  2065. array_merge($common, $transitional, $non_xml),
  2066. array('Tidy_Transitional', 'Tidy_Proprietary'),
  2067. array(),
  2068. '-//W3C//DTD HTML 4.01 Transitional//EN',
  2069. 'http://www.w3.org/TR/html4/loose.dtd'
  2070. );
  2071. $this->doctypes->register(
  2072. 'HTML 4.01 Strict', false,
  2073. array_merge($common, $non_xml),
  2074. array('Tidy_Strict', 'Tidy_Proprietary'),
  2075. array(),
  2076. '-//W3C//DTD HTML 4.01//EN',
  2077. 'http://www.w3.org/TR/html4/strict.dtd'
  2078. );
  2079. $this->doctypes->register(
  2080. 'XHTML 1.0 Transitional', true,
  2081. array_merge($common, $transitional, $xml, $non_xml),
  2082. array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary'),
  2083. array(),
  2084. '-//W3C//DTD XHTML 1.0 Transitional//EN',
  2085. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
  2086. );
  2087. $this->doctypes->register(
  2088. 'XHTML 1.0 Strict', true,
  2089. array_merge($common, $xml, $non_xml),
  2090. array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary'),
  2091. array(),
  2092. '-//W3C//DTD XHTML 1.0 Strict//EN',
  2093. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
  2094. );
  2095. $this->doctypes->register(
  2096. 'XHTML 1.1', true,
  2097. array_merge($common, $xml, array('Ruby')),
  2098. array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict'),
  2099. array(),
  2100. '-//W3C//DTD XHTML 1.1//EN',
  2101. 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
  2102. );
  2103. }
  2104. public function registerModule($module, $overload = false) {
  2105. if (is_string($module)) {
  2106. $original_module = $module;
  2107. $ok = false;
  2108. foreach ($this->prefixes as $prefix) {
  2109. $module = $prefix . $original_module;
  2110. if (class_exists($module)) {
  2111. $ok = true;
  2112. break;
  2113. }
  2114. }
  2115. if (!$ok) {
  2116. $module = $original_module;
  2117. if (!class_exists($module)) {
  2118. trigger_error($original_module . ' module does not exist',
  2119. E_USER_ERROR);
  2120. return;
  2121. }
  2122. }
  2123. $module = new $module();
  2124. }
  2125. if (empty($module->name)) {
  2126. trigger_error('Module instance of ' . get_class($module) . ' must have name');
  2127. return;
  2128. }
  2129. if (!$overload && isset($this->registeredModules[$module->name])) {
  2130. trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
  2131. }
  2132. $this->registeredModules[$module->name] = $module;
  2133. }
  2134. public function addModule($module) {
  2135. $this->registerModule($module);
  2136. if (is_object($module)) $module = $module->name;
  2137. $this->userModules[] = $module;
  2138. }
  2139. public function addPrefix($prefix) {
  2140. $this->prefixes[] = $prefix;
  2141. }
  2142. public function setup($config) {
  2143. $this->trusted = $config->get('HTML', 'Trusted');
  2144. $this->doctype = $this->doctypes->make($config);
  2145. $modules = $this->doctype->modules;
  2146. $lookup = $config->get('HTML', 'AllowedModules');
  2147. $special_cases = $config->get('HTML', 'CoreModules');
  2148. if (is_array($lookup)) {
  2149. foreach ($modules as $k => $m) {
  2150. if (isset($special_cases[$m])) continue;
  2151. if (!isset($lookup[$m])) unset($modules[$k]);
  2152. }
  2153. }
  2154. $modules = array_merge($modules, $this->userModules);
  2155. if ($config->get('HTML', 'Proprietary')) {
  2156. $modules[] = 'Proprietary';
  2157. }
  2158. foreach ($modules as $module) {
  2159. $this->processModule($module);
  2160. }
  2161. foreach ($this->doctype->tidyModules as $module) {
  2162. $this->processModule($module);
  2163. if (method_exists($this->modules[$module], 'construct')) {
  2164. $this->modules[$module]->construct($config);
  2165. }
  2166. }
  2167. foreach ($this->modules as $module) {
  2168. foreach ($module->info as $name => $def) {
  2169. if (!isset($this->elementLookup[$name])) {
  2170. $this->elementLookup[$name] = array();
  2171. }
  2172. $this->elementLookup[$name][] = $module->name;
  2173. }
  2174. }
  2175. $this->contentSets = new HTMLPurifier_ContentSets(
  2176. $this->modules
  2177. );
  2178. $this->attrCollections = new HTMLPurifier_AttrCollections(
  2179. $this->attrTypes,
  2180. $this->modules
  2181. );
  2182. }
  2183. public function processModule($module) {
  2184. if (!isset($this->registeredModules[$module]) || is_object($module)) {
  2185. $this->registerModule($module);
  2186. }
  2187. $this->modules[$module] = $this->registeredModules[$module];
  2188. }
  2189. public function getElements() {
  2190. $elements = array();
  2191. foreach ($this->modules as $module) {
  2192. if (!$this->trusted && !$module->safe) continue;
  2193. foreach ($module->info as $name => $v) {
  2194. if (isset($elements[$name])) continue;
  2195. $elements[$name] = $this->getElement($name);
  2196. }
  2197. }
  2198. foreach ($elements as $n => $v) {
  2199. if ($v === false) unset($elements[$n]);
  2200. }
  2201. return $elements;
  2202. }
  2203. public function getElement($name, $trusted = null) {
  2204. if (!isset($this->elementLookup[$name])) {
  2205. return false;
  2206. }
  2207. $def = false;
  2208. if ($trusted === null) $trusted = $this->trusted;
  2209. foreach($this->elementLookup[$name] as $module_name) {
  2210. $module = $this->modules[$module_name];
  2211. if (!$trusted && !$module->safe) {
  2212. continue;
  2213. }
  2214. $new_def = clone $module->info[$name];
  2215. if (!$def && $new_def->standalone) {
  2216. $def = $new_def;
  2217. } elseif ($def) {
  2218. $def->mergeIn($new_def);
  2219. } else {
  2220. continue;
  2221. }
  2222. $this->attrCollections->performInclusions($def->attr);
  2223. $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
  2224. if (is_string($def->content_model) &&
  2225. strpos($def->content_model, 'Inline') !== false) {
  2226. if ($name != 'del' && $name != 'ins') {
  2227. $def->descendants_are_inline = true;
  2228. }
  2229. }
  2230. $this->contentSets->generateChildDef($def, $module);
  2231. }
  2232. foreach ($def->attr as $attr_name => $attr_def) {
  2233. if ($attr_def->required) {
  2234. $def->required_attr[] = $attr_name;
  2235. }
  2236. }
  2237. return $def;
  2238. }
  2239. }
  2240. class HTMLPurifier_IDAccumulator
  2241. {
  2242. public $ids = array();
  2243. public static function build($config, $context) {
  2244. $id_accumulator = new HTMLPurifier_IDAccumulator();
  2245. $id_accumulator->load($config->get('Attr', 'IDBlacklist'));
  2246. return $id_accumulator;
  2247. }
  2248. public function add($id) {
  2249. if (isset($this->ids[$id])) return false;
  2250. return $this->ids[$id] = true;
  2251. }
  2252. public function load($array_of_ids) {
  2253. foreach ($array_of_ids as $id) {
  2254. $this->ids[$id] = true;
  2255. }
  2256. }
  2257. }
  2258. abstract class HTMLPurifier_Injector
  2259. {
  2260. public $name;
  2261. public $skip = 1;
  2262. protected $htmlDefinition;
  2263. protected $currentNesting;
  2264. protected $inputTokens;
  2265. protected $inputIndex;
  2266. public $needed = array();
  2267. public function prepare($config, $context) {
  2268. $this->htmlDefinition = $config->getHTMLDefinition();
  2269. foreach ($this->needed as $element => $attributes) {
  2270. if (is_int($element)) $element = $attributes;
  2271. if (!isset($this->htmlDefinition->info[$element])) return $element;
  2272. if (!is_array($attributes)) continue;
  2273. foreach ($attributes as $name) {
  2274. if (!isset($this->htmlDefinition->info[$element]->attr[$name])) return "$element.$name";
  2275. }
  2276. }
  2277. $this->currentNesting =& $context->get('CurrentNesting');
  2278. $this->inputTokens =& $context->get('InputTokens');
  2279. $this->inputIndex =& $context->get('InputIndex');
  2280. return false;
  2281. }
  2282. public function allowsElement($name) {
  2283. if (!empty($this->currentNesting)) {
  2284. $parent_token = array_pop($this->currentNesting);
  2285. $this->currentNesting[] = $parent_token;
  2286. $parent = $this->htmlDefinition->info[$parent_token->name];
  2287. } else {
  2288. $parent = $this->htmlDefinition->info_parent_def;
  2289. }
  2290. if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
  2291. return false;
  2292. }
  2293. return true;
  2294. }
  2295. public function handleText(&$token) {}
  2296. public function handleElement(&$token) {}
  2297. public function notifyEnd($token) {}
  2298. }
  2299. class HTMLPurifier_Language
  2300. {
  2301. public $code = 'en';
  2302. public $fallback = false;
  2303. public $messages = array();
  2304. public $errorNames = array();
  2305. public $error = false;
  2306. public $_loaded = false;
  2307. protected $config, $context;
  2308. public function __construct($config, $context) {
  2309. $this->config = $config;
  2310. $this->context = $context;
  2311. }
  2312. public function load() {
  2313. if ($this->_loaded) return;
  2314. $factory = HTMLPurifier_LanguageFactory::instance();
  2315. $factory->loadLanguage($this->code);
  2316. foreach ($factory->keys as $key) {
  2317. $this->$key = $factory->cache[$this->code][$key];
  2318. }
  2319. $this->_loaded = true;
  2320. }
  2321. public function getMessage($key) {
  2322. if (!$this->_loaded) $this->load();
  2323. if (!isset($this->messages[$key])) return "[$key]";
  2324. return $this->messages[$key];
  2325. }
  2326. public function getErrorName($int) {
  2327. if (!$this->_loaded) $this->load();
  2328. if (!isset($this->errorNames[$int])) return "[Error: $int]";
  2329. return $this->errorNames[$int];
  2330. }
  2331. public function listify($array) {
  2332. $sep = $this->getMessage('Item separator');
  2333. $sep_last = $this->getMessage('Item separator last');
  2334. $ret = '';
  2335. for ($i = 0, $c = count($array); $i < $c; $i++) {
  2336. if ($i == 0) {
  2337. } elseif ($i + 1 < $c) {
  2338. $ret .= $sep;
  2339. } else {
  2340. $ret .= $sep_last;
  2341. }
  2342. $ret .= $array[$i];
  2343. }
  2344. return $ret;
  2345. }
  2346. public function formatMessage($key, $args = array()) {
  2347. if (!$this->_loaded) $this->load();
  2348. if (!isset($this->messages[$key])) return "[$key]";
  2349. $raw = $this->messages[$key];
  2350. $subst = array();
  2351. $generator = false;
  2352. foreach ($args as $i => $value) {
  2353. if (is_object($value)) {
  2354. if ($value instanceof HTMLPurifier_Token) {
  2355. if (!$generator) $generator = $this->context->get('Generator');
  2356. if (isset($value->name)) $subst['$'.$i.'.Name'] = $value->name;
  2357. if (isset($value->data)) $subst['$'.$i.'.Data'] = $value->data;
  2358. $subst['$'.$i.'.Compact'] =
  2359. $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);
  2360. if (!empty($value->attr)) {
  2361. $stripped_token = clone $value;
  2362. $stripped_token->attr = array();
  2363. $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);
  2364. }
  2365. $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';
  2366. }
  2367. continue;
  2368. } elseif (is_array($value)) {
  2369. $keys = array_keys($value);
  2370. if (array_keys($keys) === $keys) {
  2371. $subst['$'.$i] = $this->listify($value);
  2372. } else {
  2373. $subst['$'.$i.'.Keys'] = $this->listify($keys);
  2374. $subst['$'.$i.'.Values'] = $this->listify(array_values($value));
  2375. }
  2376. continue;
  2377. }
  2378. $subst['$' . $i] = $value;
  2379. }
  2380. return strtr($raw, $subst);
  2381. }
  2382. }
  2383. class HTMLPurifier_LanguageFactory
  2384. {
  2385. public $cache;
  2386. public $keys = array('fallback', 'messages', 'errorNames');
  2387. protected $validator;
  2388. protected $dir;
  2389. protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
  2390. protected $mergeable_keys_list = array();
  2391. public static function instance($prototype = null) {
  2392. static $instance = null;
  2393. if ($prototype !== null) {
  2394. $instance = $prototype;
  2395. } elseif ($instance === null || $prototype == true) {
  2396. $instance = new HTMLPurifier_LanguageFactory();
  2397. $instance->setup();
  2398. }
  2399. return $instance;
  2400. }
  2401. public function setup() {
  2402. $this->validator = new HTMLPurifier_AttrDef_Lang();
  2403. $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
  2404. }
  2405. public function create($config, $context, $code = false) {
  2406. if ($code === false) {
  2407. $code = $this->validator->validate(
  2408. $config->get('Core', 'Language'), $config, $context
  2409. );
  2410. } else {
  2411. $code = $this->validator->validate($code, $config, $context);
  2412. }
  2413. if ($code === false) $code = 'en';
  2414. $pcode = str_replace('-', '_', $code);
  2415. static $depth = 0;
  2416. if ($code == 'en') {
  2417. $lang = new HTMLPurifier_Language($config, $context);
  2418. } else {
  2419. $class = 'HTMLPurifier_Language_' . $pcode;
  2420. $file = $this->dir . '/Language/classes/' . $code . '.php';
  2421. if (file_exists($file) || class_exists($class, false)) {
  2422. $lang = new $class($config, $context);
  2423. } else {
  2424. $raw_fallback = $this->getFallbackFor($code);
  2425. $fallback = $raw_fallback ? $raw_fallback : 'en';
  2426. $depth++;
  2427. $lang = $this->create($config, $context, $fallback);
  2428. if (!$raw_fallback) {
  2429. $lang->error = true;
  2430. }
  2431. $depth--;
  2432. }
  2433. }
  2434. $lang->code = $code;
  2435. return $lang;
  2436. }
  2437. public function getFallbackFor($code) {
  2438. $this->loadLanguage($code);
  2439. return $this->cache[$code]['fallback'];
  2440. }
  2441. public function loadLanguage($code) {
  2442. static $languages_seen = array();
  2443. if (isset($this->cache[$code])) return;
  2444. $filename = $this->dir . '/Language/messages/' . $code . '.php';
  2445. $fallback = ($code != 'en') ? 'en' : false;
  2446. if (!file_exists($filename)) {
  2447. $filename = $this->dir . '/Language/messages/en.php';
  2448. $cache = array();
  2449. } else {
  2450. include $filename;
  2451. $cache = compact($this->keys);
  2452. }
  2453. if (!empty($fallback)) {
  2454. if (isset($languages_seen[$code])) {
  2455. trigger_error('Circular fallback reference in language ' .
  2456. $code, E_USER_ERROR);
  2457. $fallback = 'en';
  2458. }
  2459. $language_seen[$code] = true;
  2460. $this->loadLanguage($fallback);
  2461. $fallback_cache = $this->cache[$fallback];
  2462. foreach ( $this->keys as $key ) {
  2463. if (isset($cache[$key]) && isset($fallback_cache[$key])) {
  2464. if (isset($this->mergeable_keys_map[$key])) {
  2465. $cache[$key] = $cache[$key] + $fallback_cache[$key];
  2466. } elseif (isset($this->mergeable_keys_list[$key])) {
  2467. $cache[$key] = array_merge( $fallback_cache[$key], $cache[$key] );
  2468. }
  2469. } else {
  2470. $cache[$key] = $fallback_cache[$key];
  2471. }
  2472. }
  2473. }
  2474. $this->cache[$code] = $cache;
  2475. return;
  2476. }
  2477. }
  2478. class HTMLPurifier_Lexer
  2479. {
  2480. public static function create($config) {
  2481. if (!($config instanceof HTMLPurifier_Config)) {
  2482. $lexer = $config;
  2483. trigger_error("Passing a prototype to
  2484. HTMLPurifier_Lexer::create() is deprecated, please instead
  2485. use %Core.LexerImpl", E_USER_WARNING);
  2486. } else {
  2487. $lexer = $config->get('Core', 'LexerImpl');
  2488. }
  2489. if (is_object($lexer)) {
  2490. return $lexer;
  2491. }
  2492. if (is_null($lexer)) { do {
  2493. $line_numbers = $config->get('Core', 'MaintainLineNumbers');
  2494. if (
  2495. $line_numbers === true ||
  2496. ($line_numbers === null && $config->get('Core', 'CollectErrors'))
  2497. ) {
  2498. $lexer = 'DirectLex';
  2499. break;
  2500. }
  2501. if (class_exists('DOMDocument')) {
  2502. $lexer = 'DOMLex';
  2503. } else {
  2504. $lexer = 'DirectLex';
  2505. }
  2506. } while(0); }
  2507. switch ($lexer) {
  2508. case 'DOMLex':
  2509. return new HTMLPurifier_Lexer_DOMLex();
  2510. case 'DirectLex':
  2511. return new HTMLPurifier_Lexer_DirectLex();
  2512. case 'PH5P':
  2513. return new HTMLPurifier_Lexer_PH5P();
  2514. default:
  2515. trigger_error("Cannot instantiate unrecognized Lexer type " . htmlspecialchars($lexer), E_USER_ERROR);
  2516. }
  2517. }
  2518. public function __construct() {
  2519. $this->_entity_parser = new HTMLPurifier_EntityParser();
  2520. }
  2521. protected $_special_entity2str =
  2522. array(
  2523. '&quot;' => '"',
  2524. '&amp;' => '&',
  2525. '&lt;' => '<',
  2526. '&gt;' => '>',
  2527. '&#39;' => "'",
  2528. '&#039;' => "'",
  2529. '&#x27;' => "'"
  2530. );
  2531. public function parseData($string) {
  2532. if ($string === '') return '';
  2533. $num_amp = substr_count($string, '&') - substr_count($string, '& ') -
  2534. ($string[strlen($string)-1] === '&' ? 1 : 0);
  2535. if (!$num_amp) return $string;
  2536. $num_esc_amp = substr_count($string, '&amp;');
  2537. $string = strtr($string, $this->_special_entity2str);
  2538. $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -
  2539. ($string[strlen($string)-1] === '&' ? 1 : 0);
  2540. if ($num_amp_2 <= $num_esc_amp) return $string;
  2541. $string = $this->_entity_parser->substituteSpecialEntities($string);
  2542. return $string;
  2543. }
  2544. public function tokenizeHTML($string, $config, $context) {
  2545. trigger_error('Call to abstract class', E_USER_ERROR);
  2546. }
  2547. protected static function escapeCDATA($string) {
  2548. return preg_replace_callback(
  2549. '/<!\[CDATA\[(.+?)\]\]>/s',
  2550. array('HTMLPurifier_Lexer', 'CDATACallback'),
  2551. $string
  2552. );
  2553. }
  2554. protected static function escapeCommentedCDATA($string) {
  2555. return preg_replace_callback(
  2556. '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
  2557. array('HTMLPurifier_Lexer', 'CDATACallback'),
  2558. $string
  2559. );
  2560. }
  2561. protected static function CDATACallback($matches) {
  2562. return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');
  2563. }
  2564. public function normalize($html, $config, $context) {
  2565. if ($config->get('Core', 'ConvertDocumentToFragment')) {
  2566. $html = $this->extractBody($html);
  2567. }
  2568. $html = str_replace("\r\n", "\n", $html);
  2569. $html = str_replace("\r", "\n", $html);
  2570. if ($config->get('HTML', 'Trusted')) {
  2571. $html = $this->escapeCommentedCDATA($html);
  2572. }
  2573. $html = $this->escapeCDATA($html);
  2574. $html = $this->_entity_parser->substituteNonSpecialEntities($html);
  2575. $html = HTMLPurifier_Encoder::cleanUTF8($html);
  2576. return $html;
  2577. }
  2578. public function extractBody($html) {
  2579. $matches = array();
  2580. $result = preg_match('!<body[^>]*>(.+?)</body>!is', $html, $matches);
  2581. if ($result) {
  2582. return $matches[1];
  2583. } else {
  2584. return $html;
  2585. }
  2586. }
  2587. }
  2588. class HTMLPurifier_PercentEncoder
  2589. {
  2590. protected $preserve = array();
  2591. public function __construct($preserve = false) {
  2592. for ($i = 48; $i <= 57; $i++) $this->preserve[$i] = true;
  2593. for ($i = 65; $i <= 90; $i++) $this->preserve[$i] = true;
  2594. for ($i = 97; $i <= 122; $i++) $this->preserve[$i] = true;
  2595. $this->preserve[45] = true;
  2596. $this->preserve[46] = true;
  2597. $this->preserve[95] = true;
  2598. $this->preserve[126]= true;
  2599. if ($preserve !== false) {
  2600. for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {
  2601. $this->preserve[ord($preserve[$i])] = true;
  2602. }
  2603. }
  2604. }
  2605. public function encode($string) {
  2606. $ret = '';
  2607. for ($i = 0, $c = strlen($string); $i < $c; $i++) {
  2608. if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])]) ) {
  2609. $ret .= '%' . sprintf('%02X', $int);
  2610. } else {
  2611. $ret .= $string[$i];
  2612. }
  2613. }
  2614. return $ret;
  2615. }
  2616. public function normalize($string) {
  2617. if ($string == '') return '';
  2618. $parts = explode('%', $string);
  2619. $ret = array_shift($parts);
  2620. foreach ($parts as $part) {
  2621. $length = strlen($part);
  2622. if ($length < 2) {
  2623. $ret .= '%25' . $part;
  2624. continue;
  2625. }
  2626. $encoding = substr($part, 0, 2);
  2627. $text = substr($part, 2);
  2628. if (!ctype_xdigit($encoding)) {
  2629. $ret .= '%25' . $part;
  2630. continue;
  2631. }
  2632. $int = hexdec($encoding);
  2633. if (isset($this->preserve[$int])) {
  2634. $ret .= chr($int) . $text;
  2635. continue;
  2636. }
  2637. $encoding = strtoupper($encoding);
  2638. $ret .= '%' . $encoding . $text;
  2639. }
  2640. return $ret;
  2641. }
  2642. }
  2643. abstract class HTMLPurifier_Strategy
  2644. {
  2645. abstract public function execute($tokens, $config, $context);
  2646. }
  2647. class HTMLPurifier_StringHash extends ArrayObject
  2648. {
  2649. protected $accessed = array();
  2650. public function offsetGet($index) {
  2651. $this->accessed[$index] = true;
  2652. return parent::offsetGet($index);
  2653. }
  2654. public function getAccessed() {
  2655. return $this->accessed;
  2656. }
  2657. public function resetAccessed() {
  2658. $this->accessed = array();
  2659. }
  2660. }
  2661. class HTMLPurifier_StringHashParser
  2662. {
  2663. public $default = 'ID';
  2664. public function parseFile($file) {
  2665. if (!file_exists($file)) return false;
  2666. $fh = fopen($file, 'r');
  2667. if (!$fh) return false;
  2668. $ret = $this->parseHandle($fh);
  2669. fclose($fh);
  2670. return $ret;
  2671. }
  2672. public function parseMultiFile($file) {
  2673. if (!file_exists($file)) return false;
  2674. $ret = array();
  2675. $fh = fopen($file, 'r');
  2676. if (!$fh) return false;
  2677. while (!feof($fh)) {
  2678. $ret[] = $this->parseHandle($fh);
  2679. }
  2680. fclose($fh);
  2681. return $ret;
  2682. }
  2683. protected function parseHandle($fh) {
  2684. $state = false;
  2685. $single = false;
  2686. $ret = array();
  2687. do {
  2688. $line = fgets($fh);
  2689. if ($line === false) break;
  2690. $line = rtrim($line, "\n\r");
  2691. if (!$state && $line === '') continue;
  2692. if ($line === '----') break;
  2693. if (strncmp('--', $line, 2) === 0) {
  2694. $state = trim($line, '- ');
  2695. continue;
  2696. } elseif (!$state) {
  2697. $single = true;
  2698. if (strpos($line, ':') !== false) {
  2699. list($state, $line) = explode(': ', $line, 2);
  2700. } else {
  2701. $state = $this->default;
  2702. }
  2703. }
  2704. if ($single) {
  2705. $ret[$state] = $line;
  2706. $single = false;
  2707. $state = false;
  2708. } else {
  2709. if (!isset($ret[$state])) $ret[$state] = '';
  2710. $ret[$state] .= "$line\n";
  2711. }
  2712. } while (!feof($fh));
  2713. return $ret;
  2714. }
  2715. }
  2716. abstract class HTMLPurifier_TagTransform
  2717. {
  2718. public $transform_to;
  2719. abstract public function transform($tag, $config, $context);
  2720. protected function prependCSS(&$attr, $css) {
  2721. $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  2722. $attr['style'] = $css . $attr['style'];
  2723. }
  2724. }
  2725. class HTMLPurifier_Token {
  2726. public $type; /**< Type of node to bypass <tt>is_a()</tt>. */
  2727. public $line; /**< Line number node was on in source document. Null if unknown. */
  2728. public $armor = array();
  2729. public function __get($n) {
  2730. if ($n === 'type') {
  2731. trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
  2732. switch (get_class($this)) {
  2733. case 'HTMLPurifier_Token_Start': return 'start';
  2734. case 'HTMLPurifier_Token_Empty': return 'empty';
  2735. case 'HTMLPurifier_Token_End': return 'end';
  2736. case 'HTMLPurifier_Token_Text': return 'text';
  2737. case 'HTMLPurifier_Token_Comment': return 'comment';
  2738. default: return null;
  2739. }
  2740. }
  2741. }
  2742. }
  2743. class HTMLPurifier_TokenFactory
  2744. {
  2745. private $p_start, $p_end, $p_empty, $p_text, $p_comment;
  2746. public function __construct() {
  2747. $this->p_start = new HTMLPurifier_Token_Start('', array());
  2748. $this->p_end = new HTMLPurifier_Token_End('');
  2749. $this->p_empty = new HTMLPurifier_Token_Empty('', array());
  2750. $this->p_text = new HTMLPurifier_Token_Text('');
  2751. $this->p_comment= new HTMLPurifier_Token_Comment('');
  2752. }
  2753. public function createStart($name, $attr = array()) {
  2754. $p = clone $this->p_start;
  2755. $p->__construct($name, $attr);
  2756. return $p;
  2757. }
  2758. public function createEnd($name) {
  2759. $p = clone $this->p_end;
  2760. $p->__construct($name);
  2761. return $p;
  2762. }
  2763. public function createEmpty($name, $attr = array()) {
  2764. $p = clone $this->p_empty;
  2765. $p->__construct($name, $attr);
  2766. return $p;
  2767. }
  2768. public function createText($data) {
  2769. $p = clone $this->p_text;
  2770. $p->__construct($data);
  2771. return $p;
  2772. }
  2773. public function createComment($data) {
  2774. $p = clone $this->p_comment;
  2775. $p->__construct($data);
  2776. return $p;
  2777. }
  2778. }
  2779. class HTMLPurifier_URI
  2780. {
  2781. public $scheme, $userinfo, $host, $port, $path, $query, $fragment;
  2782. public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment) {
  2783. $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
  2784. $this->userinfo = $userinfo;
  2785. $this->host = $host;
  2786. $this->port = is_null($port) ? $port : (int) $port;
  2787. $this->path = $path;
  2788. $this->query = $query;
  2789. $this->fragment = $fragment;
  2790. }
  2791. public function getSchemeObj($config, $context) {
  2792. $registry = HTMLPurifier_URISchemeRegistry::instance();
  2793. if ($this->scheme !== null) {
  2794. $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
  2795. if (!$scheme_obj) return false;
  2796. } else {
  2797. $def = $config->getDefinition('URI');
  2798. $scheme_obj = $registry->getScheme($def->defaultScheme, $config, $context);
  2799. if (!$scheme_obj) {
  2800. trigger_error(
  2801. 'Default scheme object "' . $def->defaultScheme . '" was not readable',
  2802. E_USER_WARNING
  2803. );
  2804. return false;
  2805. }
  2806. }
  2807. return $scheme_obj;
  2808. }
  2809. public function validate($config, $context) {
  2810. $chars_sub_delims = '!$&\'()*+,;=';
  2811. $chars_gen_delims = ':/?#[]@';
  2812. $chars_pchar = $chars_sub_delims . ':@';
  2813. if (!is_null($this->scheme) && is_null($this->host)) {
  2814. $def = $config->getDefinition('URI');
  2815. if ($def->defaultScheme === $this->scheme) {
  2816. $this->scheme = null;
  2817. }
  2818. }
  2819. if (!is_null($this->host)) {
  2820. $host_def = new HTMLPurifier_AttrDef_URI_Host();
  2821. $this->host = $host_def->validate($this->host, $config, $context);
  2822. if ($this->host === false) $this->host = null;
  2823. }
  2824. if (!is_null($this->userinfo)) {
  2825. $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
  2826. $this->userinfo = $encoder->encode($this->userinfo);
  2827. }
  2828. if (!is_null($this->port)) {
  2829. if ($this->port < 1 || $this->port > 65535) $this->port = null;
  2830. }
  2831. $path_parts = array();
  2832. $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
  2833. if (!is_null($this->host)) {
  2834. $this->path = $segments_encoder->encode($this->path);
  2835. } elseif ($this->path !== '' && $this->path[0] === '/') {
  2836. if (strlen($this->path) >= 2 && $this->path[1] === '/') {
  2837. $this->path = '';
  2838. } else {
  2839. $this->path = $segments_encoder->encode($this->path);
  2840. }
  2841. } elseif (!is_null($this->scheme) && $this->path !== '') {
  2842. $this->path = $segments_encoder->encode($this->path);
  2843. } elseif (is_null($this->scheme) && $this->path !== '') {
  2844. $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
  2845. $c = strpos($this->path, '/');
  2846. if ($c !== false) {
  2847. $this->path =
  2848. $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
  2849. $segments_encoder->encode(substr($this->path, $c));
  2850. } else {
  2851. $this->path = $segment_nc_encoder->encode($this->path);
  2852. }
  2853. } else {
  2854. $this->path = '';
  2855. }
  2856. return true;
  2857. }
  2858. public function toString() {
  2859. $authority = null;
  2860. if (!is_null($this->host)) {
  2861. $authority = '';
  2862. if(!is_null($this->userinfo)) $authority .= $this->userinfo . '@';
  2863. $authority .= $this->host;
  2864. if(!is_null($this->port)) $authority .= ':' . $this->port;
  2865. }
  2866. $result = '';
  2867. if (!is_null($this->scheme)) $result .= $this->scheme . ':';
  2868. if (!is_null($authority)) $result .= '//' . $authority;
  2869. $result .= $this->path;
  2870. if (!is_null($this->query)) $result .= '?' . $this->query;
  2871. if (!is_null($this->fragment)) $result .= '#' . $this->fragment;
  2872. return $result;
  2873. }
  2874. }
  2875. class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
  2876. {
  2877. public $type = 'URI';
  2878. protected $filters = array();
  2879. protected $registeredFilters = array();
  2880. public $base;
  2881. public $host;
  2882. public $defaultScheme;
  2883. public function __construct() {
  2884. $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
  2885. $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
  2886. $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
  2887. $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
  2888. }
  2889. public function registerFilter($filter) {
  2890. $this->registeredFilters[$filter->name] = $filter;
  2891. }
  2892. public function addFilter($filter, $config) {
  2893. $filter->prepare($config);
  2894. $this->filters[$filter->name] = $filter;
  2895. }
  2896. protected function doSetup($config) {
  2897. $this->setupMemberVariables($config);
  2898. $this->setupFilters($config);
  2899. }
  2900. protected function setupFilters($config) {
  2901. foreach ($this->registeredFilters as $name => $filter) {
  2902. $conf = $config->get('URI', $name);
  2903. if ($conf !== false && $conf !== null) {
  2904. $this->addFilter($filter, $config);
  2905. }
  2906. }
  2907. unset($this->registeredFilters);
  2908. }
  2909. protected function setupMemberVariables($config) {
  2910. $this->host = $config->get('URI', 'Host');
  2911. $base_uri = $config->get('URI', 'Base');
  2912. if (!is_null($base_uri)) {
  2913. $parser = new HTMLPurifier_URIParser();
  2914. $this->base = $parser->parse($base_uri);
  2915. $this->defaultScheme = $this->base->scheme;
  2916. if (is_null($this->host)) $this->host = $this->base->host;
  2917. }
  2918. if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI', 'DefaultScheme');
  2919. }
  2920. public function filter(&$uri, $config, $context) {
  2921. foreach ($this->filters as $name => $x) {
  2922. $result = $this->filters[$name]->filter($uri, $config, $context);
  2923. if (!$result) return false;
  2924. }
  2925. return true;
  2926. }
  2927. }
  2928. abstract class HTMLPurifier_URIFilter
  2929. {
  2930. public $name;
  2931. public function prepare($config) {}
  2932. abstract public function filter(&$uri, $config, $context);
  2933. }
  2934. class HTMLPurifier_URIParser
  2935. {
  2936. protected $percentEncoder;
  2937. public function __construct() {
  2938. $this->percentEncoder = new HTMLPurifier_PercentEncoder();
  2939. }
  2940. public function parse($uri) {
  2941. $uri = $this->percentEncoder->normalize($uri);
  2942. $r_URI = '!'.
  2943. '(([^:/?#"<>]+):)?'.
  2944. '(//([^/?#"<>]*))?'.
  2945. '([^?#"<>]*)'.
  2946. '(\?([^#"<>]*))?'.
  2947. '(#([^"<>]*))?'.
  2948. '!';
  2949. $matches = array();
  2950. $result = preg_match($r_URI, $uri, $matches);
  2951. if (!$result) return false;
  2952. $scheme = !empty($matches[1]) ? $matches[2] : null;
  2953. $authority = !empty($matches[3]) ? $matches[4] : null;
  2954. $path = $matches[5];
  2955. $query = !empty($matches[6]) ? $matches[7] : null;
  2956. $fragment = !empty($matches[8]) ? $matches[9] : null;
  2957. if ($authority !== null) {
  2958. $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
  2959. $matches = array();
  2960. preg_match($r_authority, $authority, $matches);
  2961. $userinfo = !empty($matches[1]) ? $matches[2] : null;
  2962. $host = !empty($matches[3]) ? $matches[3] : '';
  2963. $port = !empty($matches[4]) ? (int) $matches[5] : null;
  2964. } else {
  2965. $port = $host = $userinfo = null;
  2966. }
  2967. return new HTMLPurifier_URI(
  2968. $scheme, $userinfo, $host, $port, $path, $query, $fragment);
  2969. }
  2970. }
  2971. class HTMLPurifier_URIScheme
  2972. {
  2973. public $default_port = null;
  2974. public $browsable = false;
  2975. public $hierarchical = false;
  2976. public function validate(&$uri, $config, $context) {
  2977. if ($this->default_port == $uri->port) $uri->port = null;
  2978. return true;
  2979. }
  2980. }
  2981. class HTMLPurifier_URISchemeRegistry
  2982. {
  2983. public static function instance($prototype = null) {
  2984. static $instance = null;
  2985. if ($prototype !== null) {
  2986. $instance = $prototype;
  2987. } elseif ($instance === null || $prototype == true) {
  2988. $instance = new HTMLPurifier_URISchemeRegistry();
  2989. }
  2990. return $instance;
  2991. }
  2992. protected $schemes = array();
  2993. public function getScheme($scheme, $config, $context) {
  2994. if (!$config) $config = HTMLPurifier_Config::createDefault();
  2995. $null = null;
  2996. $allowed_schemes = $config->get('URI', 'AllowedSchemes');
  2997. if (!$config->get('URI', 'OverrideAllowedSchemes') &&
  2998. !isset($allowed_schemes[$scheme])
  2999. ) {
  3000. return $null;
  3001. }
  3002. if (isset($this->schemes[$scheme])) return $this->schemes[$scheme];
  3003. if (!isset($allowed_schemes[$scheme])) return $null;
  3004. $class = 'HTMLPurifier_URIScheme_' . $scheme;
  3005. if (!class_exists($class)) return $null;
  3006. $this->schemes[$scheme] = new $class();
  3007. return $this->schemes[$scheme];
  3008. }
  3009. public function register($scheme, $scheme_obj) {
  3010. $this->schemes[$scheme] = $scheme_obj;
  3011. }
  3012. }
  3013. class HTMLPurifier_VarParser
  3014. {
  3015. static public $types = array(
  3016. 'string' => true,
  3017. 'istring' => true,
  3018. 'text' => true,
  3019. 'itext' => true,
  3020. 'int' => true,
  3021. 'float' => true,
  3022. 'bool' => true,
  3023. 'lookup' => true,
  3024. 'list' => true,
  3025. 'hash' => true,
  3026. 'mixed' => true
  3027. );
  3028. static public $stringTypes = array(
  3029. 'string' => true,
  3030. 'istring' => true,
  3031. 'text' => true,
  3032. 'itext' => true,
  3033. );
  3034. final public function parse($var, $type, $allow_null = false) {
  3035. if (!isset(HTMLPurifier_VarParser::$types[$type])) {
  3036. throw new HTMLPurifier_VarParserException("Invalid type '$type'");
  3037. }
  3038. $var = $this->parseImplementation($var, $type, $allow_null);
  3039. if ($allow_null && $var === null) return null;
  3040. switch ($type) {
  3041. case 'string':
  3042. case 'istring':
  3043. case 'text':
  3044. case 'itext':
  3045. if (!is_string($var)) break;
  3046. if ($type[0] == 'i') $var = strtolower($var);
  3047. return $var;
  3048. case 'int':
  3049. if (!is_int($var)) break;
  3050. return $var;
  3051. case 'float':
  3052. if (!is_float($var)) break;
  3053. return $var;
  3054. case 'bool':
  3055. if (!is_bool($var)) break;
  3056. return $var;
  3057. case 'lookup':
  3058. case 'list':
  3059. case 'hash':
  3060. if (!is_array($var)) break;
  3061. if ($type === 'lookup') {
  3062. foreach ($var as $k) if ($k !== true) $this->error('Lookup table contains value other than true');
  3063. } elseif ($type === 'list') {
  3064. $keys = array_keys($var);
  3065. if (array_keys($keys) !== $keys) $this->error('Indices for list are not uniform');
  3066. }
  3067. return $var;
  3068. case 'mixed':
  3069. return $var;
  3070. default:
  3071. $this->errorInconsistent(get_class($this), $type);
  3072. }
  3073. $this->errorGeneric($var, $type);
  3074. }
  3075. protected function parseImplementation($var, $type, $allow_null) {
  3076. return $var;
  3077. }
  3078. protected function error($msg) {
  3079. throw new HTMLPurifier_VarParserException($msg);
  3080. }
  3081. protected function errorInconsistent($class, $type) {
  3082. throw new HTMLPurifier_Exception("Inconsistency in $class: $type not implemented");
  3083. }
  3084. protected function errorGeneric($var, $type) {
  3085. $vtype = gettype($var);
  3086. $this->error("Expected type $type, got $vtype");
  3087. }
  3088. }
  3089. class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
  3090. {
  3091. }
  3092. class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
  3093. {
  3094. public function validate($css, $config, $context) {
  3095. $css = $this->parseCDATA($css);
  3096. $definition = $config->getCSSDefinition();
  3097. $declarations = explode(';', $css);
  3098. $propvalues = array();
  3099. foreach ($declarations as $declaration) {
  3100. if (!$declaration) continue;
  3101. if (!strpos($declaration, ':')) continue;
  3102. list($property, $value) = explode(':', $declaration, 2);
  3103. $property = trim($property);
  3104. $value = trim($value);
  3105. $ok = false;
  3106. do {
  3107. if (isset($definition->info[$property])) {
  3108. $ok = true;
  3109. break;
  3110. }
  3111. if (ctype_lower($property)) break;
  3112. $property = strtolower($property);
  3113. if (isset($definition->info[$property])) {
  3114. $ok = true;
  3115. break;
  3116. }
  3117. } while(0);
  3118. if (!$ok) continue;
  3119. if (strtolower(trim($value)) !== 'inherit') {
  3120. $result = $definition->info[$property]->validate(
  3121. $value, $config, $context );
  3122. } else {
  3123. $result = 'inherit';
  3124. }
  3125. if ($result === false) continue;
  3126. $propvalues[$property] = $result;
  3127. }
  3128. $new_declarations = '';
  3129. foreach ($propvalues as $prop => $value) {
  3130. $new_declarations .= "$prop:$value;";
  3131. }
  3132. return $new_declarations ? $new_declarations : false;
  3133. }
  3134. }
  3135. class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
  3136. {
  3137. public $valid_values = array();
  3138. protected $case_sensitive = false;
  3139. public function __construct(
  3140. $valid_values = array(), $case_sensitive = false
  3141. ) {
  3142. $this->valid_values = array_flip($valid_values);
  3143. $this->case_sensitive = $case_sensitive;
  3144. }
  3145. public function validate($string, $config, $context) {
  3146. $string = trim($string);
  3147. if (!$this->case_sensitive) {
  3148. $string = ctype_lower($string) ? $string : strtolower($string);
  3149. }
  3150. $result = isset($this->valid_values[$string]);
  3151. return $result ? $string : false;
  3152. }
  3153. public function make($string) {
  3154. if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
  3155. $string = substr($string, 2);
  3156. $sensitive = true;
  3157. } else {
  3158. $sensitive = false;
  3159. }
  3160. $values = explode(',', $string);
  3161. return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
  3162. }
  3163. }
  3164. class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
  3165. {
  3166. protected $negative = true;
  3167. protected $zero = true;
  3168. protected $positive = true;
  3169. public function __construct(
  3170. $negative = true, $zero = true, $positive = true
  3171. ) {
  3172. $this->negative = $negative;
  3173. $this->zero = $zero;
  3174. $this->positive = $positive;
  3175. }
  3176. public function validate($integer, $config, $context) {
  3177. $integer = $this->parseCDATA($integer);
  3178. if ($integer === '') return false;
  3179. if ( $this->negative && $integer[0] === '-' ) {
  3180. $digits = substr($integer, 1);
  3181. if ($digits === '0') $integer = '0';
  3182. } elseif( $this->positive && $integer[0] === '+' ) {
  3183. $digits = $integer = substr($integer, 1);
  3184. } else {
  3185. $digits = $integer;
  3186. }
  3187. if (!ctype_digit($digits)) return false;
  3188. if (!$this->zero && $integer == 0) return false;
  3189. if (!$this->positive && $integer > 0) return false;
  3190. if (!$this->negative && $integer < 0) return false;
  3191. return $integer;
  3192. }
  3193. }
  3194. class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
  3195. {
  3196. public function validate($string, $config, $context) {
  3197. $string = trim($string);
  3198. if (!$string) return false;
  3199. $subtags = explode('-', $string);
  3200. $num_subtags = count($subtags);
  3201. if ($num_subtags == 0) return false;
  3202. $length = strlen($subtags[0]);
  3203. switch ($length) {
  3204. case 0:
  3205. return false;
  3206. case 1:
  3207. if (! ($subtags[0] == 'x' || $subtags[0] == 'i') ) {
  3208. return false;
  3209. }
  3210. break;
  3211. case 2:
  3212. case 3:
  3213. if (! ctype_alpha($subtags[0]) ) {
  3214. return false;
  3215. } elseif (! ctype_lower($subtags[0]) ) {
  3216. $subtags[0] = strtolower($subtags[0]);
  3217. }
  3218. break;
  3219. default:
  3220. return false;
  3221. }
  3222. $new_string = $subtags[0];
  3223. if ($num_subtags == 1) return $new_string;
  3224. $length = strlen($subtags[1]);
  3225. if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
  3226. return $new_string;
  3227. }
  3228. if (!ctype_lower($subtags[1])) $subtags[1] = strtolower($subtags[1]);
  3229. $new_string .= '-' . $subtags[1];
  3230. if ($num_subtags == 2) return $new_string;
  3231. for ($i = 2; $i < $num_subtags; $i++) {
  3232. $length = strlen($subtags[$i]);
  3233. if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
  3234. return $new_string;
  3235. }
  3236. if (!ctype_lower($subtags[$i])) {
  3237. $subtags[$i] = strtolower($subtags[$i]);
  3238. }
  3239. $new_string .= '-' . $subtags[$i];
  3240. }
  3241. return $new_string;
  3242. }
  3243. }
  3244. class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
  3245. {
  3246. public function validate($string, $config, $context) {
  3247. return $this->parseCDATA($string);
  3248. }
  3249. }
  3250. class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
  3251. {
  3252. protected $parser;
  3253. protected $embedsResource;
  3254. public function __construct($embeds_resource = false) {
  3255. $this->parser = new HTMLPurifier_URIParser();
  3256. $this->embedsResource = (bool) $embeds_resource;
  3257. }
  3258. public function validate($uri, $config, $context) {
  3259. if ($config->get('URI', 'Disable')) return false;
  3260. $uri = $this->parseCDATA($uri);
  3261. $uri = $this->parser->parse($uri);
  3262. if ($uri === false) return false;
  3263. $context->register('EmbeddedURI', $this->embedsResource);
  3264. $ok = false;
  3265. do {
  3266. $result = $uri->validate($config, $context);
  3267. if (!$result) break;
  3268. $uri_def = $config->getDefinition('URI');
  3269. $result = $uri_def->filter($uri, $config, $context);
  3270. if (!$result) break;
  3271. $scheme_obj = $uri->getSchemeObj($config, $context);
  3272. if (!$scheme_obj) break;
  3273. if ($this->embedsResource && !$scheme_obj->browsable) break;
  3274. $result = $scheme_obj->validate($uri, $config, $context);
  3275. if (!$result) break;
  3276. $ok = true;
  3277. } while (false);
  3278. $context->destroy('EmbeddedURI');
  3279. if (!$ok) return false;
  3280. $result = $uri->toString();
  3281. if (
  3282. !is_null($uri->host) &&
  3283. !empty($scheme_obj->browsable) &&
  3284. !is_null($munge = $config->get('URI', 'Munge'))
  3285. ) {
  3286. $result = str_replace('%s', rawurlencode($result), $munge);
  3287. }
  3288. return $result;
  3289. }
  3290. }
  3291. class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
  3292. {
  3293. protected $non_negative = false;
  3294. public function __construct($non_negative = false) {
  3295. $this->non_negative = $non_negative;
  3296. }
  3297. public function validate($number, $config, $context) {
  3298. $number = $this->parseCDATA($number);
  3299. if ($number === '') return false;
  3300. if ($number === '0') return '0';
  3301. $sign = '';
  3302. switch ($number[0]) {
  3303. case '-':
  3304. if ($this->non_negative) return false;
  3305. $sign = '-';
  3306. case '+':
  3307. $number = substr($number, 1);
  3308. }
  3309. if (ctype_digit($number)) {
  3310. $number = ltrim($number, '0');
  3311. return $number ? $sign . $number : '0';
  3312. }
  3313. if (strpos($number, '.') === false) return false;
  3314. list($left, $right) = explode('.', $number, 2);
  3315. if ($left === '' && $right === '') return false;
  3316. if ($left !== '' && !ctype_digit($left)) return false;
  3317. $left = ltrim($left, '0');
  3318. $right = rtrim($right, '0');
  3319. if ($right === '') {
  3320. return $left ? $sign . $left : '0';
  3321. } elseif (!ctype_digit($right)) {
  3322. return false;
  3323. }
  3324. return $sign . $left . '.' . $right;
  3325. }
  3326. }
  3327. class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
  3328. {
  3329. public function __construct() {
  3330. parent::__construct(false);
  3331. }
  3332. public function validate($number, $config, $context) {
  3333. $result = parent::validate($number, $config, $context);
  3334. if ($result === false) return $result;
  3335. $float = (float) $result;
  3336. if ($float < 0.0) $result = '0';
  3337. if ($float > 1.0) $result = '1';
  3338. return $result;
  3339. }
  3340. }
  3341. class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
  3342. {
  3343. protected $info;
  3344. public function __construct($config) {
  3345. $def = $config->getCSSDefinition();
  3346. $this->info['background-color'] = $def->info['background-color'];
  3347. $this->info['background-image'] = $def->info['background-image'];
  3348. $this->info['background-repeat'] = $def->info['background-repeat'];
  3349. $this->info['background-attachment'] = $def->info['background-attachment'];
  3350. $this->info['background-position'] = $def->info['background-position'];
  3351. }
  3352. public function validate($string, $config, $context) {
  3353. $string = $this->parseCDATA($string);
  3354. if ($string === '') return false;
  3355. $string = $this->mungeRgb($string);
  3356. $bits = explode(' ', strtolower($string));
  3357. $caught = array();
  3358. $caught['color'] = false;
  3359. $caught['image'] = false;
  3360. $caught['repeat'] = false;
  3361. $caught['attachment'] = false;
  3362. $caught['position'] = false;
  3363. $i = 0;
  3364. $none = false;
  3365. foreach ($bits as $bit) {
  3366. if ($bit === '') continue;
  3367. foreach ($caught as $key => $status) {
  3368. if ($key != 'position') {
  3369. if ($status !== false) continue;
  3370. $r = $this->info['background-' . $key]->validate($bit, $config, $context);
  3371. } else {
  3372. $r = $bit;
  3373. }
  3374. if ($r === false) continue;
  3375. if ($key == 'position') {
  3376. if ($caught[$key] === false) $caught[$key] = '';
  3377. $caught[$key] .= $r . ' ';
  3378. } else {
  3379. $caught[$key] = $r;
  3380. }
  3381. $i++;
  3382. break;
  3383. }
  3384. }
  3385. if (!$i) return false;
  3386. if ($caught['position'] !== false) {
  3387. $caught['position'] = $this->info['background-position']->
  3388. validate($caught['position'], $config, $context);
  3389. }
  3390. $ret = array();
  3391. foreach ($caught as $value) {
  3392. if ($value === false) continue;
  3393. $ret[] = $value;
  3394. }
  3395. if (empty($ret)) return false;
  3396. return implode(' ', $ret);
  3397. }
  3398. }
  3399. class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
  3400. {
  3401. protected $length;
  3402. protected $percentage;
  3403. public function __construct() {
  3404. $this->length = new HTMLPurifier_AttrDef_CSS_Length();
  3405. $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
  3406. }
  3407. public function validate($string, $config, $context) {
  3408. $string = $this->parseCDATA($string);
  3409. $bits = explode(' ', $string);
  3410. $keywords = array();
  3411. $keywords['h'] = false;
  3412. $keywords['v'] = false;
  3413. $keywords['c'] = false;
  3414. $measures = array();
  3415. $i = 0;
  3416. $lookup = array(
  3417. 'top' => 'v',
  3418. 'bottom' => 'v',
  3419. 'left' => 'h',
  3420. 'right' => 'h',
  3421. 'center' => 'c'
  3422. );
  3423. foreach ($bits as $bit) {
  3424. if ($bit === '') continue;
  3425. $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
  3426. if (isset($lookup[$lbit])) {
  3427. $status = $lookup[$lbit];
  3428. $keywords[$status] = $lbit;
  3429. $i++;
  3430. }
  3431. $r = $this->length->validate($bit, $config, $context);
  3432. if ($r !== false) {
  3433. $measures[] = $r;
  3434. $i++;
  3435. }
  3436. $r = $this->percentage->validate($bit, $config, $context);
  3437. if ($r !== false) {
  3438. $measures[] = $r;
  3439. $i++;
  3440. }
  3441. }
  3442. if (!$i) return false;
  3443. $ret = array();
  3444. if ($keywords['h']) $ret[] = $keywords['h'];
  3445. elseif (count($measures)) $ret[] = array_shift($measures);
  3446. elseif ($keywords['c']) {
  3447. $ret[] = $keywords['c'];
  3448. $keywords['c'] = false;
  3449. }
  3450. if ($keywords['v']) $ret[] = $keywords['v'];
  3451. elseif (count($measures)) $ret[] = array_shift($measures);
  3452. elseif ($keywords['c']) $ret[] = $keywords['c'];
  3453. if (empty($ret)) return false;
  3454. return implode(' ', $ret);
  3455. }
  3456. }
  3457. class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
  3458. {
  3459. protected $info = array();
  3460. public function __construct($config) {
  3461. $def = $config->getCSSDefinition();
  3462. $this->info['border-width'] = $def->info['border-width'];
  3463. $this->info['border-style'] = $def->info['border-style'];
  3464. $this->info['border-top-color'] = $def->info['border-top-color'];
  3465. }
  3466. public function validate($string, $config, $context) {
  3467. $string = $this->parseCDATA($string);
  3468. $string = $this->mungeRgb($string);
  3469. $bits = explode(' ', $string);
  3470. $done = array();
  3471. $ret = '';
  3472. foreach ($bits as $bit) {
  3473. foreach ($this->info as $propname => $validator) {
  3474. if (isset($done[$propname])) continue;
  3475. $r = $validator->validate($bit, $config, $context);
  3476. if ($r !== false) {
  3477. $ret .= $r . ' ';
  3478. $done[$propname] = true;
  3479. break;
  3480. }
  3481. }
  3482. }
  3483. return rtrim($ret);
  3484. }
  3485. }
  3486. class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
  3487. {
  3488. public function validate($color, $config, $context) {
  3489. static $colors = null;
  3490. if ($colors === null) $colors = $config->get('Core', 'ColorKeywords');
  3491. $color = trim($color);
  3492. if ($color === '') return false;
  3493. $lower = strtolower($color);
  3494. if (isset($colors[$lower])) return $colors[$lower];
  3495. if (strpos($color, 'rgb(') !== false) {
  3496. $length = strlen($color);
  3497. if (strpos($color, ')') !== $length - 1) return false;
  3498. $triad = substr($color, 4, $length - 4 - 1);
  3499. $parts = explode(',', $triad);
  3500. if (count($parts) !== 3) return false;
  3501. $type = false;
  3502. $new_parts = array();
  3503. foreach ($parts as $part) {
  3504. $part = trim($part);
  3505. if ($part === '') return false;
  3506. $length = strlen($part);
  3507. if ($part[$length - 1] === '%') {
  3508. if (!$type) {
  3509. $type = 'percentage';
  3510. } elseif ($type !== 'percentage') {
  3511. return false;
  3512. }
  3513. $num = (float) substr($part, 0, $length - 1);
  3514. if ($num < 0) $num = 0;
  3515. if ($num > 100) $num = 100;
  3516. $new_parts[] = "$num%";
  3517. } else {
  3518. if (!$type) {
  3519. $type = 'integer';
  3520. } elseif ($type !== 'integer') {
  3521. return false;
  3522. }
  3523. $num = (int) $part;
  3524. if ($num < 0) $num = 0;
  3525. if ($num > 255) $num = 255;
  3526. $new_parts[] = (string) $num;
  3527. }
  3528. }
  3529. $new_triad = implode(',', $new_parts);
  3530. $color = "rgb($new_triad)";
  3531. } else {
  3532. if ($color[0] === '#') {
  3533. $hex = substr($color, 1);
  3534. } else {
  3535. $hex = $color;
  3536. $color = '#' . $color;
  3537. }
  3538. $length = strlen($hex);
  3539. if ($length !== 3 && $length !== 6) return false;
  3540. if (!ctype_xdigit($hex)) return false;
  3541. }
  3542. return $color;
  3543. }
  3544. }
  3545. class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
  3546. {
  3547. public $defs;
  3548. public function __construct($defs) {
  3549. $this->defs = $defs;
  3550. }
  3551. public function validate($string, $config, $context) {
  3552. foreach ($this->defs as $i => $def) {
  3553. $result = $this->defs[$i]->validate($string, $config, $context);
  3554. if ($result !== false) return $result;
  3555. }
  3556. return false;
  3557. }
  3558. }
  3559. class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
  3560. {
  3561. protected $def, $element;
  3562. public function __construct($def, $element) {
  3563. $this->def = $def;
  3564. $this->element = $element;
  3565. }
  3566. public function validate($string, $config, $context) {
  3567. $token = $context->get('CurrentToken', true);
  3568. if ($token && $token->name == $this->element) return false;
  3569. return $this->def->validate($string, $config, $context);
  3570. }
  3571. }
  3572. class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
  3573. {
  3574. protected $intValidator;
  3575. public function __construct() {
  3576. $this->intValidator = new HTMLPurifier_AttrDef_Integer();
  3577. }
  3578. public function validate($value, $config, $context) {
  3579. $value = $this->parseCDATA($value);
  3580. if ($value === 'none') return $value;
  3581. $function_length = strcspn($value, '(');
  3582. $function = trim(substr($value, 0, $function_length));
  3583. if ($function !== 'alpha' &&
  3584. $function !== 'Alpha' &&
  3585. $function !== 'progid:DXImageTransform.Microsoft.Alpha'
  3586. ) return false;
  3587. $cursor = $function_length + 1;
  3588. $parameters_length = strcspn($value, ')', $cursor);
  3589. $parameters = substr($value, $cursor, $parameters_length);
  3590. $params = explode(',', $parameters);
  3591. $ret_params = array();
  3592. $lookup = array();
  3593. foreach ($params as $param) {
  3594. list($key, $value) = explode('=', $param);
  3595. $key = trim($key);
  3596. $value = trim($value);
  3597. if (isset($lookup[$key])) continue;
  3598. if ($key !== 'opacity') continue;
  3599. $value = $this->intValidator->validate($value, $config, $context);
  3600. if ($value === false) continue;
  3601. $int = (int) $value;
  3602. if ($int > 100) $value = '100';
  3603. if ($int < 0) $value = '0';
  3604. $ret_params[] = "$key=$value";
  3605. $lookup[$key] = true;
  3606. }
  3607. $ret_parameters = implode(',', $ret_params);
  3608. $ret_function = "$function($ret_parameters)";
  3609. return $ret_function;
  3610. }
  3611. }
  3612. class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
  3613. {
  3614. protected $info = array();
  3615. public function __construct($config) {
  3616. $def = $config->getCSSDefinition();
  3617. $this->info['font-style'] = $def->info['font-style'];
  3618. $this->info['font-variant'] = $def->info['font-variant'];
  3619. $this->info['font-weight'] = $def->info['font-weight'];
  3620. $this->info['font-size'] = $def->info['font-size'];
  3621. $this->info['line-height'] = $def->info['line-height'];
  3622. $this->info['font-family'] = $def->info['font-family'];
  3623. }
  3624. public function validate($string, $config, $context) {
  3625. static $system_fonts = array(
  3626. 'caption' => true,
  3627. 'icon' => true,
  3628. 'menu' => true,
  3629. 'message-box' => true,
  3630. 'small-caption' => true,
  3631. 'status-bar' => true
  3632. );
  3633. $string = $this->parseCDATA($string);
  3634. if ($string === '') return false;
  3635. $lowercase_string = strtolower($string);
  3636. if (isset($system_fonts[$lowercase_string])) {
  3637. return $lowercase_string;
  3638. }
  3639. $bits = explode(' ', $string);
  3640. $stage = 0;
  3641. $caught = array();
  3642. $stage_1 = array('font-style', 'font-variant', 'font-weight');
  3643. $final = '';
  3644. for ($i = 0, $size = count($bits); $i < $size; $i++) {
  3645. if ($bits[$i] === '') continue;
  3646. switch ($stage) {
  3647. case 0:
  3648. foreach ($stage_1 as $validator_name) {
  3649. if (isset($caught[$validator_name])) continue;
  3650. $r = $this->info[$validator_name]->validate(
  3651. $bits[$i], $config, $context);
  3652. if ($r !== false) {
  3653. $final .= $r . ' ';
  3654. $caught[$validator_name] = true;
  3655. break;
  3656. }
  3657. }
  3658. if (count($caught) >= 3) $stage = 1;
  3659. if ($r !== false) break;
  3660. case 1:
  3661. $found_slash = false;
  3662. if (strpos($bits[$i], '/') !== false) {
  3663. list($font_size, $line_height) =
  3664. explode('/', $bits[$i]);
  3665. if ($line_height === '') {
  3666. $line_height = false;
  3667. $found_slash = true;
  3668. }
  3669. } else {
  3670. $font_size = $bits[$i];
  3671. $line_height = false;
  3672. }
  3673. $r = $this->info['font-size']->validate(
  3674. $font_size, $config, $context);
  3675. if ($r !== false) {
  3676. $final .= $r;
  3677. if ($line_height === false) {
  3678. for ($j = $i + 1; $j < $size; $j++) {
  3679. if ($bits[$j] === '') continue;
  3680. if ($bits[$j] === '/') {
  3681. if ($found_slash) {
  3682. return false;
  3683. } else {
  3684. $found_slash = true;
  3685. continue;
  3686. }
  3687. }
  3688. $line_height = $bits[$j];
  3689. break;
  3690. }
  3691. } else {
  3692. $found_slash = true;
  3693. $j = $i;
  3694. }
  3695. if ($found_slash) {
  3696. $i = $j;
  3697. $r = $this->info['line-height']->validate(
  3698. $line_height, $config, $context);
  3699. if ($r !== false) {
  3700. $final .= '/' . $r;
  3701. }
  3702. }
  3703. $final .= ' ';
  3704. $stage = 2;
  3705. break;
  3706. }
  3707. return false;
  3708. case 2:
  3709. $font_family =
  3710. implode(' ', array_slice($bits, $i, $size - $i));
  3711. $r = $this->info['font-family']->validate(
  3712. $font_family, $config, $context);
  3713. if ($r !== false) {
  3714. $final .= $r . ' ';
  3715. return rtrim($final);
  3716. }
  3717. return false;
  3718. }
  3719. }
  3720. return false;
  3721. }
  3722. }
  3723. class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
  3724. {
  3725. public function validate($string, $config, $context) {
  3726. static $generic_names = array(
  3727. 'serif' => true,
  3728. 'sans-serif' => true,
  3729. 'monospace' => true,
  3730. 'fantasy' => true,
  3731. 'cursive' => true
  3732. );
  3733. $string = $this->parseCDATA($string);
  3734. $fonts = explode(',', $string);
  3735. $final = '';
  3736. foreach($fonts as $font) {
  3737. $font = trim($font);
  3738. if ($font === '') continue;
  3739. if (isset($generic_names[$font])) {
  3740. $final .= $font . ', ';
  3741. continue;
  3742. }
  3743. if ($font[0] === '"' || $font[0] === "'") {
  3744. $length = strlen($font);
  3745. if ($length <= 2) continue;
  3746. $quote = $font[0];
  3747. if ($font[$length - 1] !== $quote) continue;
  3748. $font = substr($font, 1, $length - 2);
  3749. $font = str_replace("\\$quote", $quote, $font);
  3750. $font = str_replace("\\\n", "\n", $font);
  3751. }
  3752. if (ctype_alnum($font)) {
  3753. $final .= $font . ', ';
  3754. continue;
  3755. }
  3756. $font = str_replace("'", "\\'", $font);
  3757. $font = str_replace("\n", "\\\n", $font);
  3758. $final .= "'$font', ";
  3759. }
  3760. $final = rtrim($final, ', ');
  3761. if ($final === '') return false;
  3762. return $final;
  3763. }
  3764. }
  3765. class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
  3766. {
  3767. protected $def, $allow;
  3768. public function __construct($def, $allow = false) {
  3769. $this->def = $def;
  3770. $this->allow = $allow;
  3771. }
  3772. public function validate($string, $config, $context) {
  3773. $string = trim($string);
  3774. $is_important = false;
  3775. if (strlen($string) >= 9 && substr($string, -9) === 'important') {
  3776. $temp = rtrim(substr($string, 0, -9));
  3777. if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
  3778. $string = rtrim(substr($temp, 0, -1));
  3779. $is_important = true;
  3780. }
  3781. }
  3782. $string = $this->def->validate($string, $config, $context);
  3783. if ($this->allow && $is_important) $string .= ' !important';
  3784. return $string;
  3785. }
  3786. }
  3787. class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
  3788. {
  3789. protected $units = array('em' => true, 'ex' => true, 'px' => true, 'in' => true,
  3790. 'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true);
  3791. protected $number_def;
  3792. public function __construct($non_negative = false) {
  3793. $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
  3794. }
  3795. public function validate($length, $config, $context) {
  3796. $length = $this->parseCDATA($length);
  3797. if ($length === '') return false;
  3798. if ($length === '0') return '0';
  3799. $strlen = strlen($length);
  3800. if ($strlen === 1) return false;
  3801. $unit = substr($length, $strlen - 2);
  3802. if (!ctype_lower($unit)) $unit = strtolower($unit);
  3803. $number = substr($length, 0, $strlen - 2);
  3804. if (!isset($this->units[$unit])) return false;
  3805. $number = $this->number_def->validate($number, $config, $context);
  3806. if ($number === false) return false;
  3807. return $number . $unit;
  3808. }
  3809. }
  3810. class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
  3811. {
  3812. protected $info;
  3813. public function __construct($config) {
  3814. $def = $config->getCSSDefinition();
  3815. $this->info['list-style-type'] = $def->info['list-style-type'];
  3816. $this->info['list-style-position'] = $def->info['list-style-position'];
  3817. $this->info['list-style-image'] = $def->info['list-style-image'];
  3818. }
  3819. public function validate($string, $config, $context) {
  3820. $string = $this->parseCDATA($string);
  3821. if ($string === '') return false;
  3822. $bits = explode(' ', strtolower($string));
  3823. $caught = array();
  3824. $caught['type'] = false;
  3825. $caught['position'] = false;
  3826. $caught['image'] = false;
  3827. $i = 0;
  3828. $none = false;
  3829. foreach ($bits as $bit) {
  3830. if ($i >= 3) return;
  3831. if ($bit === '') continue;
  3832. foreach ($caught as $key => $status) {
  3833. if ($status !== false) continue;
  3834. $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
  3835. if ($r === false) continue;
  3836. if ($r === 'none') {
  3837. if ($none) continue;
  3838. else $none = true;
  3839. if ($key == 'image') continue;
  3840. }
  3841. $caught[$key] = $r;
  3842. $i++;
  3843. break;
  3844. }
  3845. }
  3846. if (!$i) return false;
  3847. $ret = array();
  3848. if ($caught['type']) $ret[] = $caught['type'];
  3849. if ($caught['image']) $ret[] = $caught['image'];
  3850. if ($caught['position']) $ret[] = $caught['position'];
  3851. if (empty($ret)) return false;
  3852. return implode(' ', $ret);
  3853. }
  3854. }
  3855. class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
  3856. {
  3857. public $single;
  3858. public $max;
  3859. public function __construct($single, $max = 4) {
  3860. $this->single = $single;
  3861. $this->max = $max;
  3862. }
  3863. public function validate($string, $config, $context) {
  3864. $string = $this->parseCDATA($string);
  3865. if ($string === '') return false;
  3866. $parts = explode(' ', $string);
  3867. $length = count($parts);
  3868. $final = '';
  3869. for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
  3870. if (ctype_space($parts[$i])) continue;
  3871. $result = $this->single->validate($parts[$i], $config, $context);
  3872. if ($result !== false) {
  3873. $final .= $result . ' ';
  3874. $num++;
  3875. }
  3876. }
  3877. if ($final === '') return false;
  3878. return rtrim($final);
  3879. }
  3880. }
  3881. class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
  3882. {
  3883. protected $number_def;
  3884. public function __construct($non_negative = false) {
  3885. $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
  3886. }
  3887. public function validate($string, $config, $context) {
  3888. $string = $this->parseCDATA($string);
  3889. if ($string === '') return false;
  3890. $length = strlen($string);
  3891. if ($length === 1) return false;
  3892. if ($string[$length - 1] !== '%') return false;
  3893. $number = substr($string, 0, $length - 1);
  3894. $number = $this->number_def->validate($number, $config, $context);
  3895. if ($number === false) return false;
  3896. return "$number%";
  3897. }
  3898. }
  3899. class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
  3900. {
  3901. public function validate($string, $config, $context) {
  3902. static $allowed_values = array(
  3903. 'line-through' => true,
  3904. 'overline' => true,
  3905. 'underline' => true
  3906. );
  3907. $string = strtolower($this->parseCDATA($string));
  3908. $parts = explode(' ', $string);
  3909. $final = '';
  3910. foreach ($parts as $part) {
  3911. if (isset($allowed_values[$part])) {
  3912. $final .= $part . ' ';
  3913. }
  3914. }
  3915. $final = rtrim($final);
  3916. if ($final === '') return false;
  3917. return $final;
  3918. }
  3919. }
  3920. class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
  3921. {
  3922. public function __construct() {
  3923. parent::__construct(true);
  3924. }
  3925. public function validate($uri_string, $config, $context) {
  3926. $uri_string = $this->parseCDATA($uri_string);
  3927. if (strpos($uri_string, 'url(') !== 0) return false;
  3928. $uri_string = substr($uri_string, 4);
  3929. $new_length = strlen($uri_string) - 1;
  3930. if ($uri_string[$new_length] != ')') return false;
  3931. $uri = trim(substr($uri_string, 0, $new_length));
  3932. if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
  3933. $quote = $uri[0];
  3934. $new_length = strlen($uri) - 1;
  3935. if ($uri[$new_length] !== $quote) return false;
  3936. $uri = substr($uri, 1, $new_length - 1);
  3937. }
  3938. $keys = array( '(', ')', ',', ' ', '"', "'");
  3939. $values = array('\\(', '\\)', '\\,', '\\ ', '\\"', "\\'");
  3940. $uri = str_replace($values, $keys, $uri);
  3941. $result = parent::validate($uri, $config, $context);
  3942. if ($result === false) return false;
  3943. $result = str_replace($keys, $values, $result);
  3944. return "url($result)";
  3945. }
  3946. }
  3947. class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
  3948. {
  3949. protected $name;
  3950. public $minimized = true;
  3951. public function __construct($name = false) {$this->name = $name;}
  3952. public function validate($string, $config, $context) {
  3953. if (empty($string)) return false;
  3954. return $this->name;
  3955. }
  3956. public function make($string) {
  3957. return new HTMLPurifier_AttrDef_HTML_Bool($string);
  3958. }
  3959. }
  3960. class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
  3961. {
  3962. public function validate($string, $config, $context) {
  3963. static $colors = null;
  3964. if ($colors === null) $colors = $config->get('Core', 'ColorKeywords');
  3965. $string = trim($string);
  3966. if (empty($string)) return false;
  3967. if (isset($colors[$string])) return $colors[$string];
  3968. if ($string[0] === '#') $hex = substr($string, 1);
  3969. else $hex = $string;
  3970. $length = strlen($hex);
  3971. if ($length !== 3 && $length !== 6) return false;
  3972. if (!ctype_xdigit($hex)) return false;
  3973. if ($length === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
  3974. return "#$hex";
  3975. }
  3976. }
  3977. class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
  3978. {
  3979. public $valid_values = false;
  3980. protected $case_sensitive = false;
  3981. public function __construct() {}
  3982. public function validate($string, $config, $context) {
  3983. if ($this->valid_values === false) $this->valid_values = $config->get('Attr', 'AllowedFrameTargets');
  3984. return parent::validate($string, $config, $context);
  3985. }
  3986. }
  3987. class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
  3988. {
  3989. public function validate($id, $config, $context) {
  3990. if (!$config->get('Attr', 'EnableID')) return false;
  3991. $id = trim($id);
  3992. if ($id === '') return false;
  3993. $prefix = $config->get('Attr', 'IDPrefix');
  3994. if ($prefix !== '') {
  3995. $prefix .= $config->get('Attr', 'IDPrefixLocal');
  3996. if (strpos($id, $prefix) !== 0) $id = $prefix . $id;
  3997. } elseif ($config->get('Attr', 'IDPrefixLocal') !== '') {
  3998. trigger_error('%Attr.IDPrefixLocal cannot be used unless '.
  3999. '%Attr.IDPrefix is set', E_USER_WARNING);
  4000. }
  4001. $id_accumulator =& $context->get('IDAccumulator');
  4002. if (isset($id_accumulator->ids[$id])) return false;
  4003. if (ctype_alpha($id)) {
  4004. $result = true;
  4005. } else {
  4006. if (!ctype_alpha(@$id[0])) return false;
  4007. $trim = trim(
  4008. $id,
  4009. 'A..Za..z0..9:-._'
  4010. );
  4011. $result = ($trim === '');
  4012. }
  4013. $regexp = $config->get('Attr', 'IDBlacklistRegexp');
  4014. if ($regexp && preg_match($regexp, $id)) {
  4015. return false;
  4016. }
  4017. if (/*!$this->ref && */$result) $id_accumulator->add($id);
  4018. return $result ? $id : false;
  4019. }
  4020. }
  4021. class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
  4022. {
  4023. public function validate($string, $config, $context) {
  4024. $string = trim($string);
  4025. if ($string === '0') return $string;
  4026. if ($string === '') return false;
  4027. $length = strlen($string);
  4028. if (substr($string, $length - 2) == 'px') {
  4029. $string = substr($string, 0, $length - 2);
  4030. }
  4031. if (!is_numeric($string)) return false;
  4032. $int = (int) $string;
  4033. if ($int < 0) return '0';
  4034. if ($int > 1200) return '1200';
  4035. return (string) $int;
  4036. }
  4037. }
  4038. class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
  4039. {
  4040. public function validate($string, $config, $context) {
  4041. $string = trim($string);
  4042. if ($string === '') return false;
  4043. $parent_result = parent::validate($string, $config, $context);
  4044. if ($parent_result !== false) return $parent_result;
  4045. $length = strlen($string);
  4046. $last_char = $string[$length - 1];
  4047. if ($last_char !== '%') return false;
  4048. $points = substr($string, 0, $length - 1);
  4049. if (!is_numeric($points)) return false;
  4050. $points = (int) $points;
  4051. if ($points < 0) return '0%';
  4052. if ($points > 100) return '100%';
  4053. return ((string) $points) . '%';
  4054. }
  4055. }
  4056. class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
  4057. {
  4058. /** Name config attribute to pull. */
  4059. protected $name;
  4060. public function __construct($name) {
  4061. $configLookup = array(
  4062. 'rel' => 'AllowedRel',
  4063. 'rev' => 'AllowedRev'
  4064. );
  4065. if (!isset($configLookup[$name])) {
  4066. trigger_error('Unrecognized attribute name for link '.
  4067. 'relationship.', E_USER_ERROR);
  4068. return;
  4069. }
  4070. $this->name = $configLookup[$name];
  4071. }
  4072. public function validate($string, $config, $context) {
  4073. $allowed = $config->get('Attr', $this->name);
  4074. if (empty($allowed)) return false;
  4075. $string = $this->parseCDATA($string);
  4076. $parts = explode(' ', $string);
  4077. $ret_lookup = array();
  4078. foreach ($parts as $part) {
  4079. $part = strtolower(trim($part));
  4080. if (!isset($allowed[$part])) continue;
  4081. $ret_lookup[$part] = true;
  4082. }
  4083. if (empty($ret_lookup)) return false;
  4084. $ret_array = array();
  4085. foreach ($ret_lookup as $part => $bool) $ret_array[] = $part;
  4086. $string = implode(' ', $ret_array);
  4087. return $string;
  4088. }
  4089. }
  4090. class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
  4091. {
  4092. public function validate($string, $config, $context) {
  4093. $string = trim($string);
  4094. if ($string === '') return false;
  4095. $parent_result = parent::validate($string, $config, $context);
  4096. if ($parent_result !== false) return $parent_result;
  4097. $length = strlen($string);
  4098. $last_char = $string[$length - 1];
  4099. if ($last_char !== '*') return false;
  4100. $int = substr($string, 0, $length - 1);
  4101. if ($int == '') return '*';
  4102. if (!is_numeric($int)) return false;
  4103. $int = (int) $int;
  4104. if ($int < 0) return false;
  4105. if ($int == 0) return '0';
  4106. if ($int == 1) return '*';
  4107. return ((string) $int) . '*';
  4108. }
  4109. }
  4110. class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
  4111. {
  4112. public function validate($string, $config, $context) {
  4113. $string = trim($string);
  4114. if (!$string) return false;
  4115. $matches = array();
  4116. $pattern = '/(?:(?<=\s)|\A)'.
  4117. '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)'.
  4118. '(?:(?=\s)|\z)/';
  4119. preg_match_all($pattern, $string, $matches);
  4120. if (empty($matches[1])) return false;
  4121. $new_string = '';
  4122. foreach ($matches[1] as $token) {
  4123. $new_string .= $token . ' ';
  4124. }
  4125. $new_string = rtrim($new_string);
  4126. return $new_string;
  4127. }
  4128. }
  4129. abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
  4130. {
  4131. function unpack($string) {
  4132. }
  4133. }
  4134. class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
  4135. {
  4136. protected $ipv4;
  4137. protected $ipv6;
  4138. public function __construct() {
  4139. $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
  4140. $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
  4141. }
  4142. public function validate($string, $config, $context) {
  4143. $length = strlen($string);
  4144. if ($string === '') return '';
  4145. if ($length > 1 && $string[0] === '[' && $string[$length-1] === ']') {
  4146. $ip = substr($string, 1, $length - 2);
  4147. $valid = $this->ipv6->validate($ip, $config, $context);
  4148. if ($valid === false) return false;
  4149. return '['. $valid . ']';
  4150. }
  4151. $ipv4 = $this->ipv4->validate($string, $config, $context);
  4152. if ($ipv4 !== false) return $ipv4;
  4153. $a = '[a-z]';
  4154. $an = '[a-z0-9]';
  4155. $and = '[a-z0-9-]';
  4156. $domainlabel = "$an($and*$an)?";
  4157. $toplabel = "$a($and*$an)?";
  4158. $match = preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string);
  4159. if (!$match) return false;
  4160. return $string;
  4161. }
  4162. }
  4163. class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
  4164. {
  4165. protected $ip4;
  4166. public function validate($aIP, $config, $context) {
  4167. if (!$this->ip4) $this->_loadRegex();
  4168. if (preg_match('#^' . $this->ip4 . '$#s', $aIP))
  4169. {
  4170. return $aIP;
  4171. }
  4172. return false;
  4173. }
  4174. protected function _loadRegex() {
  4175. $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])';
  4176. $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
  4177. }
  4178. }
  4179. class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
  4180. {
  4181. public function validate($aIP, $config, $context) {
  4182. if (!$this->ip4) $this->_loadRegex();
  4183. $original = $aIP;
  4184. $hex = '[0-9a-fA-F]';
  4185. $blk = '(?:' . $hex . '{1,4})';
  4186. $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))';
  4187. if (strpos($aIP, '/') !== false)
  4188. {
  4189. if (preg_match('#' . $pre . '$#s', $aIP, $find))
  4190. {
  4191. $aIP = substr($aIP, 0, 0-strlen($find[0]));
  4192. unset($find);
  4193. }
  4194. else
  4195. {
  4196. return false;
  4197. }
  4198. }
  4199. if (preg_match('#(?<=:'.')' . $this->ip4 . '$#s', $aIP, $find))
  4200. {
  4201. $aIP = substr($aIP, 0, 0-strlen($find[0]));
  4202. $ip = explode('.', $find[0]);
  4203. $ip = array_map('dechex', $ip);
  4204. $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
  4205. unset($find, $ip);
  4206. }
  4207. $aIP = explode('::', $aIP);
  4208. $c = count($aIP);
  4209. if ($c > 2)
  4210. {
  4211. return false;
  4212. }
  4213. elseif ($c == 2)
  4214. {
  4215. list($first, $second) = $aIP;
  4216. $first = explode(':', $first);
  4217. $second = explode(':', $second);
  4218. if (count($first) + count($second) > 8)
  4219. {
  4220. return false;
  4221. }
  4222. while(count($first) < 8)
  4223. {
  4224. array_push($first, '0');
  4225. }
  4226. array_splice($first, 8 - count($second), 8, $second);
  4227. $aIP = $first;
  4228. unset($first,$second);
  4229. }
  4230. else
  4231. {
  4232. $aIP = explode(':', $aIP[0]);
  4233. }
  4234. $c = count($aIP);
  4235. if ($c != 8)
  4236. {
  4237. return false;
  4238. }
  4239. foreach ($aIP as $piece)
  4240. {
  4241. if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece)))
  4242. {
  4243. return false;
  4244. }
  4245. }
  4246. return $original;
  4247. }
  4248. }
  4249. class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
  4250. {
  4251. public function validate($string, $config, $context) {
  4252. if ($string == '') return false;
  4253. $string = trim($string);
  4254. $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
  4255. return $result ? $string : false;
  4256. }
  4257. }
  4258. class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
  4259. {
  4260. public function transform($attr, $config, $context) {
  4261. if (isset($attr['dir'])) return $attr;
  4262. $attr['dir'] = $config->get('Attr', 'DefaultTextDir');
  4263. return $attr;
  4264. }
  4265. }
  4266. class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform {
  4267. public function transform($attr, $config, $context) {
  4268. if (!isset($attr['bgcolor'])) return $attr;
  4269. $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
  4270. $this->prependCSS($attr, "background-color:$bgcolor;");
  4271. return $attr;
  4272. }
  4273. }
  4274. class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform {
  4275. protected $attr;
  4276. protected $css;
  4277. public function __construct($attr, $css) {
  4278. $this->attr = $attr;
  4279. $this->css = $css;
  4280. }
  4281. public function transform($attr, $config, $context) {
  4282. if (!isset($attr[$this->attr])) return $attr;
  4283. unset($attr[$this->attr]);
  4284. $this->prependCSS($attr, $this->css);
  4285. return $attr;
  4286. }
  4287. }
  4288. class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform {
  4289. public function transform($attr, $config, $context) {
  4290. if (!isset($attr['border'])) return $attr;
  4291. $border_width = $this->confiscateAttr($attr, 'border');
  4292. $this->prependCSS($attr, "border:{$border_width}px solid;");
  4293. return $attr;
  4294. }
  4295. }
  4296. class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform {
  4297. protected $attr;
  4298. protected $enumToCSS = array();
  4299. protected $caseSensitive = false;
  4300. public function __construct($attr, $enum_to_css, $case_sensitive = false) {
  4301. $this->attr = $attr;
  4302. $this->enumToCSS = $enum_to_css;
  4303. $this->caseSensitive = (bool) $case_sensitive;
  4304. }
  4305. public function transform($attr, $config, $context) {
  4306. if (!isset($attr[$this->attr])) return $attr;
  4307. $value = trim($attr[$this->attr]);
  4308. unset($attr[$this->attr]);
  4309. if (!$this->caseSensitive) $value = strtolower($value);
  4310. if (!isset($this->enumToCSS[$value])) {
  4311. return $attr;
  4312. }
  4313. $this->prependCSS($attr, $this->enumToCSS[$value]);
  4314. return $attr;
  4315. }
  4316. }
  4317. class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
  4318. {
  4319. public function transform($attr, $config, $context) {
  4320. $src = true;
  4321. if (!isset($attr['src'])) {
  4322. if ($config->get('Core', 'RemoveInvalidImg')) return $attr;
  4323. $attr['src'] = $config->get('Attr', 'DefaultInvalidImage');
  4324. $src = false;
  4325. }
  4326. if (!isset($attr['alt'])) {
  4327. if ($src) {
  4328. $attr['alt'] = basename($attr['src']);
  4329. } else {
  4330. $attr['alt'] = $config->get('Attr', 'DefaultInvalidImageAlt');
  4331. }
  4332. }
  4333. return $attr;
  4334. }
  4335. }
  4336. class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform {
  4337. protected $attr;
  4338. protected $css = array(
  4339. 'hspace' => array('left', 'right'),
  4340. 'vspace' => array('top', 'bottom')
  4341. );
  4342. public function __construct($attr) {
  4343. $this->attr = $attr;
  4344. if (!isset($this->css[$attr])) {
  4345. trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
  4346. }
  4347. }
  4348. public function transform($attr, $config, $context) {
  4349. if (!isset($attr[$this->attr])) return $attr;
  4350. $width = $this->confiscateAttr($attr, $this->attr);
  4351. if (!isset($this->css[$this->attr])) return $attr;
  4352. $style = '';
  4353. foreach ($this->css[$this->attr] as $suffix) {
  4354. $property = "margin-$suffix";
  4355. $style .= "$property:{$width}px;";
  4356. }
  4357. $this->prependCSS($attr, $style);
  4358. return $attr;
  4359. }
  4360. }
  4361. class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
  4362. {
  4363. public function transform($attr, $config, $context) {
  4364. $lang = isset($attr['lang']) ? $attr['lang'] : false;
  4365. $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
  4366. if ($lang !== false && $xml_lang === false) {
  4367. $attr['xml:lang'] = $lang;
  4368. } elseif ($xml_lang !== false) {
  4369. $attr['lang'] = $xml_lang;
  4370. }
  4371. return $attr;
  4372. }
  4373. }
  4374. class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
  4375. {
  4376. protected $name;
  4377. protected $cssName;
  4378. public function __construct($name, $css_name = null) {
  4379. $this->name = $name;
  4380. $this->cssName = $css_name ? $css_name : $name;
  4381. }
  4382. public function transform($attr, $config, $context) {
  4383. if (!isset($attr[$this->name])) return $attr;
  4384. $length = $this->confiscateAttr($attr, $this->name);
  4385. if(ctype_digit($length)) $length .= 'px';
  4386. $this->prependCSS($attr, $this->cssName . ":$length;");
  4387. return $attr;
  4388. }
  4389. }
  4390. class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
  4391. {
  4392. public function transform($attr, $config, $context) {
  4393. if (!isset($attr['name'])) return $attr;
  4394. $id = $this->confiscateAttr($attr, 'name');
  4395. if ( isset($attr['id'])) return $attr;
  4396. $attr['id'] = $id;
  4397. return $attr;
  4398. }
  4399. }
  4400. class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
  4401. {
  4402. public function transform($attr, $config, $context) {
  4403. if (!isset($attr['type'])) {
  4404. $attr['type'] = 'text/javascript';
  4405. }
  4406. return $attr;
  4407. }
  4408. }
  4409. class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
  4410. {
  4411. public $inline;
  4412. public $block;
  4413. public $type = 'chameleon';
  4414. public function __construct($inline, $block) {
  4415. $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
  4416. $this->block = new HTMLPurifier_ChildDef_Optional($block);
  4417. $this->elements = $this->block->elements;
  4418. }
  4419. public function validateChildren($tokens_of_children, $config, $context) {
  4420. if ($context->get('IsInline') === false) {
  4421. return $this->block->validateChildren(
  4422. $tokens_of_children, $config, $context);
  4423. } else {
  4424. return $this->inline->validateChildren(
  4425. $tokens_of_children, $config, $context);
  4426. }
  4427. }
  4428. }
  4429. class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
  4430. {
  4431. public $type = 'custom';
  4432. public $allow_empty = false;
  4433. public $dtd_regex;
  4434. private $_pcre_regex;
  4435. public function __construct($dtd_regex) {
  4436. $this->dtd_regex = $dtd_regex;
  4437. $this->_compileRegex();
  4438. }
  4439. protected function _compileRegex() {
  4440. $raw = str_replace(' ', '', $this->dtd_regex);
  4441. if ($raw{0} != '(') {
  4442. $raw = "($raw)";
  4443. }
  4444. $el = '[#a-zA-Z0-9_.-]+';
  4445. $reg = $raw;
  4446. preg_match_all("/$el/", $reg, $matches);
  4447. foreach ($matches[0] as $match) {
  4448. $this->elements[$match] = true;
  4449. }
  4450. $reg = preg_replace("/$el/", '(,\\0)', $reg);
  4451. $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
  4452. $reg = preg_replace("/,\(/", '(', $reg);
  4453. $this->_pcre_regex = $reg;
  4454. }
  4455. public function validateChildren($tokens_of_children, $config, $context) {
  4456. $list_of_children = '';
  4457. $nesting = 0;
  4458. foreach ($tokens_of_children as $token) {
  4459. if (!empty($token->is_whitespace)) continue;
  4460. $is_child = ($nesting == 0);
  4461. if ($token instanceof HTMLPurifier_Token_Start) {
  4462. $nesting++;
  4463. } elseif ($token instanceof HTMLPurifier_Token_End) {
  4464. $nesting--;
  4465. }
  4466. if ($is_child) {
  4467. $list_of_children .= $token->name . ',';
  4468. }
  4469. }
  4470. $list_of_children = ',' . rtrim($list_of_children, ',');
  4471. $okay =
  4472. preg_match(
  4473. '/^,?'.$this->_pcre_regex.'$/',
  4474. $list_of_children
  4475. );
  4476. return (bool) $okay;
  4477. }
  4478. }
  4479. class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
  4480. {
  4481. public $allow_empty = true;
  4482. public $type = 'empty';
  4483. public function __construct() {}
  4484. public function validateChildren($tokens_of_children, $config, $context) {
  4485. return array();
  4486. }
  4487. }
  4488. class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
  4489. {
  4490. public $elements = array();
  4491. public function __construct($elements) {
  4492. if (is_string($elements)) {
  4493. $elements = str_replace(' ', '', $elements);
  4494. $elements = explode('|', $elements);
  4495. }
  4496. $keys = array_keys($elements);
  4497. if ($keys == array_keys($keys)) {
  4498. $elements = array_flip($elements);
  4499. foreach ($elements as $i => $x) {
  4500. $elements[$i] = true;
  4501. if (empty($i)) unset($elements[$i]);
  4502. }
  4503. }
  4504. $this->elements = $elements;
  4505. }
  4506. public $allow_empty = false;
  4507. public $type = 'required';
  4508. public function validateChildren($tokens_of_children, $config, $context) {
  4509. if (empty($tokens_of_children)) return false;
  4510. $result = array();
  4511. $nesting = 0;
  4512. $is_deleting = false;
  4513. $pcdata_allowed = isset($this->elements['#PCDATA']);
  4514. $all_whitespace = true;
  4515. $escape_invalid_children = $config->get('Core', 'EscapeInvalidChildren');
  4516. static $gen = null;
  4517. if ($gen === null) {
  4518. $gen = new HTMLPurifier_Generator();
  4519. }
  4520. foreach ($tokens_of_children as $token) {
  4521. if (!empty($token->is_whitespace)) {
  4522. $result[] = $token;
  4523. continue;
  4524. }
  4525. $all_whitespace = false;
  4526. $is_child = ($nesting == 0);
  4527. if ($token instanceof HTMLPurifier_Token_Start) {
  4528. $nesting++;
  4529. } elseif ($token instanceof HTMLPurifier_Token_End) {
  4530. $nesting--;
  4531. }
  4532. if ($is_child) {
  4533. $is_deleting = false;
  4534. if (!isset($this->elements[$token->name])) {
  4535. $is_deleting = true;
  4536. if ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text) {
  4537. $result[] = $token;
  4538. } elseif ($pcdata_allowed && $escape_invalid_children) {
  4539. $result[] = new HTMLPurifier_Token_Text(
  4540. $gen->generateFromToken($token, $config)
  4541. );
  4542. }
  4543. continue;
  4544. }
  4545. }
  4546. if (!$is_deleting || ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text)) {
  4547. $result[] = $token;
  4548. } elseif ($pcdata_allowed && $escape_invalid_children) {
  4549. $result[] =
  4550. new HTMLPurifier_Token_Text(
  4551. $gen->generateFromToken( $token, $config )
  4552. );
  4553. } else {
  4554. }
  4555. }
  4556. if (empty($result)) return false;
  4557. if ($all_whitespace) return false;
  4558. if ($tokens_of_children == $result) return true;
  4559. return $result;
  4560. }
  4561. }
  4562. class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
  4563. {
  4564. public $allow_empty = true;
  4565. public $type = 'optional';
  4566. public function validateChildren($tokens_of_children, $config, $context) {
  4567. $result = parent::validateChildren($tokens_of_children, $config, $context);
  4568. if ($result === false) {
  4569. if (empty($tokens_of_children)) return true;
  4570. else return array();
  4571. }
  4572. return $result;
  4573. }
  4574. }
  4575. class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
  4576. {
  4577. protected $real_elements;
  4578. protected $fake_elements;
  4579. public $allow_empty = true;
  4580. public $type = 'strictblockquote';
  4581. protected $init = false;
  4582. public function validateChildren($tokens_of_children, $config, $context) {
  4583. $def = $config->getHTMLDefinition();
  4584. if (!$this->init) {
  4585. $this->real_elements = $this->elements;
  4586. $this->fake_elements = $def->info_content_sets['Flow'];
  4587. $this->fake_elements['#PCDATA'] = true;
  4588. $this->init = true;
  4589. }
  4590. $this->elements = $this->fake_elements;
  4591. $result = parent::validateChildren($tokens_of_children, $config, $context);
  4592. $this->elements = $this->real_elements;
  4593. if ($result === false) return array();
  4594. if ($result === true) $result = $tokens_of_children;
  4595. $block_wrap_start = new HTMLPurifier_Token_Start($def->info_block_wrapper);
  4596. $block_wrap_end = new HTMLPurifier_Token_End( $def->info_block_wrapper);
  4597. $is_inline = false;
  4598. $depth = 0;
  4599. $ret = array();
  4600. foreach ($result as $i => $token) {
  4601. $token = $result[$i];
  4602. if (!$is_inline) {
  4603. if (!$depth) {
  4604. if (
  4605. ($token instanceof HTMLPurifier_Token_Text && !$token->is_whitespace) ||
  4606. (!$token instanceof HTMLPurifier_Token_Text && !isset($this->elements[$token->name]))
  4607. ) {
  4608. $is_inline = true;
  4609. $ret[] = $block_wrap_start;
  4610. }
  4611. }
  4612. } else {
  4613. if (!$depth) {
  4614. if ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) {
  4615. if (isset($this->elements[$token->name])) {
  4616. $ret[] = $block_wrap_end;
  4617. $is_inline = false;
  4618. }
  4619. }
  4620. }
  4621. }
  4622. $ret[] = $token;
  4623. if ($token instanceof HTMLPurifier_Token_Start) $depth++;
  4624. if ($token instanceof HTMLPurifier_Token_End) $depth--;
  4625. }
  4626. if ($is_inline) $ret[] = $block_wrap_end;
  4627. return $ret;
  4628. }
  4629. }
  4630. class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
  4631. {
  4632. public $allow_empty = false;
  4633. public $type = 'table';
  4634. public $elements = array('tr' => true, 'tbody' => true, 'thead' => true,
  4635. 'tfoot' => true, 'caption' => true, 'colgroup' => true, 'col' => true);
  4636. public function __construct() {}
  4637. public function validateChildren($tokens_of_children, $config, $context) {
  4638. if (empty($tokens_of_children)) return false;
  4639. $tokens_of_children[] = false;
  4640. $caption = false;
  4641. $thead = false;
  4642. $tfoot = false;
  4643. $cols = array();
  4644. $content = array();
  4645. $nesting = 0;
  4646. $is_collecting = false;
  4647. $collection = array();
  4648. $tag_index = 0;
  4649. foreach ($tokens_of_children as $token) {
  4650. $is_child = ($nesting == 0);
  4651. if ($token === false) {
  4652. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  4653. $nesting++;
  4654. } elseif ($token instanceof HTMLPurifier_Token_End) {
  4655. $nesting--;
  4656. }
  4657. if ($is_collecting) {
  4658. if ($is_child) {
  4659. switch ($collection[$tag_index]->name) {
  4660. case 'tr':
  4661. case 'tbody':
  4662. $content[] = $collection;
  4663. break;
  4664. case 'caption':
  4665. if ($caption !== false) break;
  4666. $caption = $collection;
  4667. break;
  4668. case 'thead':
  4669. case 'tfoot':
  4670. $var = $collection[$tag_index]->name;
  4671. if ($$var === false) {
  4672. $$var = $collection;
  4673. } else {
  4674. $collection[$tag_index]->name = 'tbody';
  4675. $collection[count($collection)-1]->name = 'tbody';
  4676. $content[] = $collection;
  4677. }
  4678. break;
  4679. case 'colgroup':
  4680. $cols[] = $collection;
  4681. break;
  4682. }
  4683. $collection = array();
  4684. $is_collecting = false;
  4685. $tag_index = 0;
  4686. } else {
  4687. $collection[] = $token;
  4688. }
  4689. }
  4690. if ($token === false) break;
  4691. if ($is_child) {
  4692. if ($token->name == 'col') {
  4693. $cols[] = array_merge($collection, array($token));
  4694. $collection = array();
  4695. $tag_index = 0;
  4696. continue;
  4697. }
  4698. switch($token->name) {
  4699. case 'caption':
  4700. case 'colgroup':
  4701. case 'thead':
  4702. case 'tfoot':
  4703. case 'tbody':
  4704. case 'tr':
  4705. $is_collecting = true;
  4706. $collection[] = $token;
  4707. continue;
  4708. default:
  4709. if ($token instanceof HTMLPurifier_Token_Text && $token->is_whitespace) {
  4710. $collection[] = $token;
  4711. $tag_index++;
  4712. }
  4713. continue;
  4714. }
  4715. }
  4716. }
  4717. if (empty($content)) return false;
  4718. $ret = array();
  4719. if ($caption !== false) $ret = array_merge($ret, $caption);
  4720. if ($cols !== false) foreach ($cols as $token_array) $ret = array_merge($ret, $token_array);
  4721. if ($thead !== false) $ret = array_merge($ret, $thead);
  4722. if ($tfoot !== false) $ret = array_merge($ret, $tfoot);
  4723. foreach ($content as $token_array) $ret = array_merge($ret, $token_array);
  4724. if (!empty($collection) && $is_collecting == false){
  4725. $ret = array_merge($ret, $collection);
  4726. }
  4727. array_pop($tokens_of_children);
  4728. return ($ret === $tokens_of_children) ? true : $ret;
  4729. }
  4730. }
  4731. class HTMLPurifier_ConfigDef_Directive extends HTMLPurifier_ConfigDef
  4732. {
  4733. public $class = 'directive';
  4734. public function __construct(
  4735. $type = null,
  4736. $allow_null = null,
  4737. $allowed = null,
  4738. $aliases = null
  4739. ) {
  4740. if ( $type !== null) $this->type = $type;
  4741. if ( $allow_null !== null) $this->allow_null = $allow_null;
  4742. if ( $allowed !== null) $this->allowed = $allowed;
  4743. if ( $aliases !== null) $this->aliases = $aliases;
  4744. }
  4745. public $type = 'mixed';
  4746. public $allow_null = false;
  4747. public $allowed = true;
  4748. public $aliases = array();
  4749. }
  4750. class HTMLPurifier_ConfigDef_DirectiveAlias extends HTMLPurifier_ConfigDef
  4751. {
  4752. public $class = 'alias';
  4753. public $namespace;
  4754. public $name;
  4755. public function __construct($namespace, $name) {
  4756. $this->namespace = $namespace;
  4757. $this->name = $name;
  4758. }
  4759. }
  4760. class HTMLPurifier_ConfigDef_Namespace extends HTMLPurifier_ConfigDef
  4761. {
  4762. public $class = 'namespace';
  4763. }
  4764. class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
  4765. {
  4766. public $cache;
  4767. public function __construct() {}
  4768. public function decorate(&$cache) {
  4769. $decorator = $this->copy();
  4770. $decorator->cache =& $cache;
  4771. $decorator->type = $cache->type;
  4772. return $decorator;
  4773. }
  4774. public function copy() {
  4775. return new HTMLPurifier_DefinitionCache_Decorator();
  4776. }
  4777. public function add($def, $config) {
  4778. return $this->cache->add($def, $config);
  4779. }
  4780. public function set($def, $config) {
  4781. return $this->cache->set($def, $config);
  4782. }
  4783. public function replace($def, $config) {
  4784. return $this->cache->replace($def, $config);
  4785. }
  4786. public function get($config) {
  4787. return $this->cache->get($config);
  4788. }
  4789. public function remove($config) {
  4790. return $this->cache->remove($config);
  4791. }
  4792. public function flush($config) {
  4793. return $this->cache->flush($config);
  4794. }
  4795. public function cleanup($config) {
  4796. return $this->cache->cleanup($config);
  4797. }
  4798. }
  4799. class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
  4800. {
  4801. public function add($def, $config) {
  4802. return false;
  4803. }
  4804. public function set($def, $config) {
  4805. return false;
  4806. }
  4807. public function replace($def, $config) {
  4808. return false;
  4809. }
  4810. public function remove($config) {
  4811. return false;
  4812. }
  4813. public function get($config) {
  4814. return false;
  4815. }
  4816. public function flush($config) {
  4817. return false;
  4818. }
  4819. public function cleanup($config) {
  4820. return false;
  4821. }
  4822. }
  4823. class HTMLPurifier_DefinitionCache_Serializer extends
  4824. HTMLPurifier_DefinitionCache
  4825. {
  4826. public function add($def, $config) {
  4827. if (!$this->checkDefType($def)) return;
  4828. $file = $this->generateFilePath($config);
  4829. if (file_exists($file)) return false;
  4830. if (!$this->_prepareDir($config)) return false;
  4831. return $this->_write($file, serialize($def));
  4832. }
  4833. public function set($def, $config) {
  4834. if (!$this->checkDefType($def)) return;
  4835. $file = $this->generateFilePath($config);
  4836. if (!$this->_prepareDir($config)) return false;
  4837. return $this->_write($file, serialize($def));
  4838. }
  4839. public function replace($def, $config) {
  4840. if (!$this->checkDefType($def)) return;
  4841. $file = $this->generateFilePath($config);
  4842. if (!file_exists($file)) return false;
  4843. if (!$this->_prepareDir($config)) return false;
  4844. return $this->_write($file, serialize($def));
  4845. }
  4846. public function get($config) {
  4847. $file = $this->generateFilePath($config);
  4848. if (!file_exists($file)) return false;
  4849. return unserialize(file_get_contents($file));
  4850. }
  4851. public function remove($config) {
  4852. $file = $this->generateFilePath($config);
  4853. if (!file_exists($file)) return false;
  4854. return unlink($file);
  4855. }
  4856. public function flush($config) {
  4857. if (!$this->_prepareDir($config)) return false;
  4858. $dir = $this->generateDirectoryPath($config);
  4859. $dh = opendir($dir);
  4860. while (false !== ($filename = readdir($dh))) {
  4861. if (empty($filename)) continue;
  4862. if ($filename[0] === '.') continue;
  4863. unlink($dir . '/' . $filename);
  4864. }
  4865. }
  4866. public function cleanup($config) {
  4867. if (!$this->_prepareDir($config)) return false;
  4868. $dir = $this->generateDirectoryPath($config);
  4869. $dh = opendir($dir);
  4870. while (false !== ($filename = readdir($dh))) {
  4871. if (empty($filename)) continue;
  4872. if ($filename[0] === '.') continue;
  4873. $key = substr($filename, 0, strlen($filename) - 4);
  4874. if ($this->isOld($key, $config)) unlink($dir . '/' . $filename);
  4875. }
  4876. }
  4877. public function generateFilePath($config) {
  4878. $key = $this->generateKey($config);
  4879. return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
  4880. }
  4881. public function generateDirectoryPath($config) {
  4882. return A_PREFIX .'cached/guard';
  4883. }
  4884. private function _write($file, $data) {
  4885. return file_put_contents($file, $data);
  4886. }
  4887. private function _prepareDir($config) {
  4888. $directory = $this->generateDirectoryPath($config);
  4889. return $this->_testPermissions($directory) ;
  4890. }
  4891. private function _testPermissions($dir) {
  4892. if (is_writable($dir)) return true;
  4893. if (!is_dir($dir)) {
  4894. trigger_error('Directory '.$dir.' does not exist',
  4895. E_USER_ERROR);
  4896. return false;
  4897. }
  4898. if (function_exists('posix_getuid')) {
  4899. if (fileowner($dir) === posix_getuid()) {
  4900. chmod($dir, 0755);
  4901. return true;
  4902. } elseif (filegroup($dir) === posix_getgid()) {
  4903. $chmod = '775';
  4904. } else {
  4905. $chmod = '777';
  4906. }
  4907. trigger_error('Directory '.$dir.' not writable, '.
  4908. 'please chmod to ' . $chmod,
  4909. E_USER_ERROR);
  4910. } else {
  4911. trigger_error('Directory '.$dir.' not writable, '.
  4912. 'please alter file permissions',
  4913. E_USER_ERROR);
  4914. }
  4915. return false;
  4916. }
  4917. }
  4918. class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends
  4919. HTMLPurifier_DefinitionCache_Decorator
  4920. {
  4921. public $name = 'Cleanup';
  4922. public function copy() {
  4923. return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
  4924. }
  4925. public function add($def, $config) {
  4926. $status = parent::add($def, $config);
  4927. if (!$status) parent::cleanup($config);
  4928. return $status;
  4929. }
  4930. public function set($def, $config) {
  4931. $status = parent::set($def, $config);
  4932. if (!$status) parent::cleanup($config);
  4933. return $status;
  4934. }
  4935. public function replace($def, $config) {
  4936. $status = parent::replace($def, $config);
  4937. if (!$status) parent::cleanup($config);
  4938. return $status;
  4939. }
  4940. public function get($config) {
  4941. $ret = parent::get($config);
  4942. if (!$ret) parent::cleanup($config);
  4943. return $ret;
  4944. }
  4945. }
  4946. class HTMLPurifier_DefinitionCache_Decorator_Memory extends
  4947. HTMLPurifier_DefinitionCache_Decorator
  4948. {
  4949. protected $definitions;
  4950. public $name = 'Memory';
  4951. public function copy() {
  4952. return new HTMLPurifier_DefinitionCache_Decorator_Memory();
  4953. }
  4954. public function add($def, $config) {
  4955. $status = parent::add($def, $config);
  4956. if ($status) $this->definitions[$this->generateKey($config)] = $def;
  4957. return $status;
  4958. }
  4959. public function set($def, $config) {
  4960. $status = parent::set($def, $config);
  4961. if ($status) $this->definitions[$this->generateKey($config)] = $def;
  4962. return $status;
  4963. }
  4964. public function replace($def, $config) {
  4965. $status = parent::replace($def, $config);
  4966. if ($status) $this->definitions[$this->generateKey($config)] = $def;
  4967. return $status;
  4968. }
  4969. public function get($config) {
  4970. $key = $this->generateKey($config);
  4971. if (isset($this->definitions[$key])) return $this->definitions[$key];
  4972. $this->definitions[$key] = parent::get($config);
  4973. return $this->definitions[$key];
  4974. }
  4975. }
  4976. class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
  4977. {
  4978. public $name = 'Bdo';
  4979. public $attr_collections = array(
  4980. 'I18N' => array('dir' => false)
  4981. );
  4982. public function __construct() {
  4983. $bdo = $this->addElement(
  4984. 'bdo', 'Inline', 'Inline', array('Core', 'Lang'),
  4985. array(
  4986. 'dir' => 'Enum#ltr,rtl',
  4987. )
  4988. );
  4989. $bdo->attr_transform_post['required-dir'] = new HTMLPurifier_AttrTransform_BdoDir();
  4990. $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
  4991. }
  4992. }
  4993. class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
  4994. {
  4995. public $name = 'CommonAttributes';
  4996. public $attr_collections = array(
  4997. 'Core' => array(
  4998. 0 => array('Style'),
  4999. 'class' => 'NMTOKENS',
  5000. 'id' => 'ID',
  5001. 'title' => 'CDATA',
  5002. ),
  5003. 'Lang' => array(),
  5004. 'I18N' => array(
  5005. 0 => array('Lang'),
  5006. ),
  5007. 'Common' => array(
  5008. 0 => array('Core', 'I18N')
  5009. )
  5010. );
  5011. }
  5012. class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
  5013. {
  5014. public $name = 'Edit';
  5015. public function __construct() {
  5016. $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
  5017. $attr = array(
  5018. 'cite' => 'URI',
  5019. );
  5020. $this->addElement('del', 'Inline', $contents, 'Common', $attr);
  5021. $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
  5022. }
  5023. public $defines_child_def = true;
  5024. public function getChildDef($def) {
  5025. if ($def->content_model_type != 'chameleon') return false;
  5026. $value = explode('!', $def->content_model);
  5027. return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
  5028. }
  5029. }
  5030. class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
  5031. {
  5032. public $name = 'Hypertext';
  5033. public function __construct() {
  5034. $a = $this->addElement(
  5035. 'a', 'Inline', 'Inline', 'Common',
  5036. array(
  5037. 'href' => 'URI',
  5038. 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
  5039. 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
  5040. )
  5041. );
  5042. $a->excludes = array('a' => true);
  5043. }
  5044. }
  5045. class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
  5046. {
  5047. public $name = 'Image';
  5048. public function __construct() {
  5049. $img = $this->addElement(
  5050. 'img', 'Inline', 'Empty', 'Common',
  5051. array(
  5052. 'alt*' => 'Text',
  5053. 'height' => 'Length',
  5054. 'longdesc' => 'URI',
  5055. 'src*' => new HTMLPurifier_AttrDef_URI(true),
  5056. 'width' => 'Length'
  5057. )
  5058. );
  5059. $img->attr_transform_pre[] =
  5060. $img->attr_transform_post[] =
  5061. new HTMLPurifier_AttrTransform_ImgRequired();
  5062. }
  5063. }
  5064. class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
  5065. {
  5066. public $name = 'Legacy';
  5067. public function __construct() {
  5068. $this->addElement('basefont', 'Inline', 'Empty', false, array(
  5069. 'color' => 'Color',
  5070. 'face' => 'Text',
  5071. 'size' => 'Text',
  5072. 'id' => 'ID'
  5073. ));
  5074. $this->addElement('center', 'Block', 'Flow', 'Common');
  5075. $this->addElement('dir', 'Block', 'Required: li', 'Common', array(
  5076. 'compact' => 'Bool#compact'
  5077. ));
  5078. $this->addElement('font', 'Inline', 'Inline', array('Core', 'I18N'), array(
  5079. 'color' => 'Color',
  5080. 'face' => 'Text',
  5081. 'size' => 'Text',
  5082. ));
  5083. $this->addElement('menu', 'Block', 'Required: li', 'Common', array(
  5084. 'compact' => 'Bool#compact'
  5085. ));
  5086. $this->addElement('s', 'Inline', 'Inline', 'Common');
  5087. $this->addElement('strike', 'Inline', 'Inline', 'Common');
  5088. $this->addElement('u', 'Inline', 'Inline', 'Common');
  5089. $align = 'Enum#left,right,center,justify';
  5090. $address = $this->addBlankElement('address');
  5091. $address->content_model = 'Inline | #PCDATA | p';
  5092. $address->content_model_type = 'optional';
  5093. $address->child = false;
  5094. $blockquote = $this->addBlankElement('blockquote');
  5095. $blockquote->content_model = 'Flow | #PCDATA';
  5096. $blockquote->content_model_type = 'optional';
  5097. $blockquote->child = false;
  5098. $br = $this->addBlankElement('br');
  5099. $br->attr['clear'] = 'Enum#left,all,right,none';
  5100. $caption = $this->addBlankElement('caption');
  5101. $caption->attr['align'] = 'Enum#top,bottom,left,right';
  5102. $div = $this->addBlankElement('div');
  5103. $div->attr['align'] = $align;
  5104. $dl = $this->addBlankElement('dl');
  5105. $dl->attr['compact'] = 'Bool#compact';
  5106. for ($i = 1; $i <= 6; $i++) {
  5107. $h = $this->addBlankElement("h$i");
  5108. $h->attr['align'] = $align;
  5109. }
  5110. $hr = $this->addBlankElement('hr');
  5111. $hr->attr['align'] = $align;
  5112. $hr->attr['noshade'] = 'Bool#noshade';
  5113. $hr->attr['size'] = 'Pixels';
  5114. $hr->attr['width'] = 'Length';
  5115. $img = $this->addBlankElement('img');
  5116. $img->attr['align'] = 'Enum#top,middle,bottom,left,right';
  5117. $img->attr['border'] = 'Pixels';
  5118. $img->attr['hspace'] = 'Pixels';
  5119. $img->attr['vspace'] = 'Pixels';
  5120. $li = $this->addBlankElement('li');
  5121. $li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
  5122. $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';
  5123. $ol = $this->addBlankElement('ol');
  5124. $ol->attr['compact'] = 'Bool#compact';
  5125. $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
  5126. $ol->attr['type'] = 'Enum#s:1,i,I,a,A';
  5127. $p = $this->addBlankElement('p');
  5128. $p->attr['align'] = $align;
  5129. $pre = $this->addBlankElement('pre');
  5130. $pre->attr['width'] = 'Number';
  5131. $table = $this->addBlankElement('table');
  5132. $table->attr['align'] = 'Enum#left,center,right';
  5133. $table->attr['bgcolor'] = 'Color';
  5134. $tr = $this->addBlankElement('tr');
  5135. $tr->attr['bgcolor'] = 'Color';
  5136. $th = $this->addBlankElement('th');
  5137. $th->attr['bgcolor'] = 'Color';
  5138. $th->attr['height'] = 'Length';
  5139. $th->attr['nowrap'] = 'Bool#nowrap';
  5140. $th->attr['width'] = 'Length';
  5141. $td = $this->addBlankElement('td');
  5142. $td->attr['bgcolor'] = 'Color';
  5143. $td->attr['height'] = 'Length';
  5144. $td->attr['nowrap'] = 'Bool#nowrap';
  5145. $td->attr['width'] = 'Length';
  5146. $ul = $this->addBlankElement('ul');
  5147. $ul->attr['compact'] = 'Bool#compact';
  5148. $ul->attr['type'] = 'Enum#square,disc,circle';
  5149. }
  5150. }
  5151. class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
  5152. {
  5153. public $name = 'List';
  5154. public $content_sets = array('Flow' => 'List');
  5155. public function __construct() {
  5156. $this->addElement('ol', 'List', 'Required: li', 'Common');
  5157. $this->addElement('ul', 'List', 'Required: li', 'Common');
  5158. $this->addElement('dl', 'List', 'Required: dt | dd', 'Common');
  5159. $this->addElement('li', false, 'Flow', 'Common');
  5160. $this->addElement('dd', false, 'Flow', 'Common');
  5161. $this->addElement('dt', false, 'Inline', 'Common');
  5162. }
  5163. }
  5164. class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
  5165. {
  5166. public $name = 'NonXMLCommonAttributes';
  5167. public $attr_collections = array(
  5168. 'Lang' => array(
  5169. 'lang' => 'LanguageCode',
  5170. )
  5171. );
  5172. }
  5173. class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
  5174. {
  5175. public $name = 'Object';
  5176. public $safe = false;
  5177. public function __construct() {
  5178. $this->addElement('object', 'Inline', 'Optional: #PCDATA | Flow | param', 'Common',
  5179. array(
  5180. 'archive' => 'URI',
  5181. 'classid' => 'URI',
  5182. 'codebase' => 'URI',
  5183. 'codetype' => 'Text',
  5184. 'data' => 'URI',
  5185. 'declare' => 'Bool#declare',
  5186. 'height' => 'Length',
  5187. 'name' => 'CDATA',
  5188. 'standby' => 'Text',
  5189. 'tabindex' => 'Number',
  5190. 'type' => 'ContentType',
  5191. 'width' => 'Length'
  5192. )
  5193. );
  5194. $this->addElement('param', false, 'Empty', false,
  5195. array(
  5196. 'id' => 'ID',
  5197. 'name*' => 'Text',
  5198. 'type' => 'Text',
  5199. 'value' => 'Text',
  5200. 'valuetype' => 'Enum#data,ref,object'
  5201. )
  5202. );
  5203. }
  5204. }
  5205. class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
  5206. {
  5207. public $name = 'Presentation';
  5208. public function __construct() {
  5209. $this->addElement('b', 'Inline', 'Inline', 'Common');
  5210. $this->addElement('big', 'Inline', 'Inline', 'Common');
  5211. $this->addElement('hr', 'Block', 'Empty', 'Common');
  5212. $this->addElement('i', 'Inline', 'Inline', 'Common');
  5213. $this->addElement('small', 'Inline', 'Inline', 'Common');
  5214. $this->addElement('sub', 'Inline', 'Inline', 'Common');
  5215. $this->addElement('sup', 'Inline', 'Inline', 'Common');
  5216. $this->addElement('tt', 'Inline', 'Inline', 'Common');
  5217. }
  5218. }
  5219. class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
  5220. {
  5221. public $name = 'Proprietary';
  5222. public function __construct() {
  5223. $this->addElement('marquee', 'Inline', 'Flow', 'Common',
  5224. array(
  5225. 'direction' => 'Enum#left,right,up,down',
  5226. 'behavior' => 'Enum#alternate',
  5227. 'width' => 'Length',
  5228. 'height' => 'Length',
  5229. 'scrolldelay' => 'Number',
  5230. 'scrollamount' => 'Number',
  5231. 'loop' => 'Number',
  5232. 'bgcolor' => 'Color',
  5233. 'hspace' => 'Pixels',
  5234. 'vspace' => 'Pixels',
  5235. )
  5236. );
  5237. }
  5238. }
  5239. class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
  5240. {
  5241. public $name = 'Ruby';
  5242. public function __construct() {
  5243. $this->addElement('ruby', 'Inline',
  5244. 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
  5245. 'Common');
  5246. $this->addElement('rbc', false, 'Required: rb', 'Common');
  5247. $this->addElement('rtc', false, 'Required: rt', 'Common');
  5248. $rb = $this->addElement('rb', false, 'Inline', 'Common');
  5249. $rb->excludes = array('ruby' => true);
  5250. $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));
  5251. $rt->excludes = array('ruby' => true);
  5252. $this->addElement('rp', false, 'Optional: #PCDATA', 'Common');
  5253. }
  5254. }
  5255. class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
  5256. {
  5257. public $name = 'Scripting';
  5258. public $elements = array('script', 'noscript');
  5259. public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
  5260. public $safe = false;
  5261. public function __construct() {
  5262. $this->info['noscript'] = new HTMLPurifier_ElementDef();
  5263. $this->info['noscript']->attr = array( 0 => array('Common') );
  5264. $this->info['noscript']->content_model = 'Heading | List | Block';
  5265. $this->info['noscript']->content_model_type = 'required';
  5266. $this->info['script'] = new HTMLPurifier_ElementDef();
  5267. $this->info['script']->attr = array(
  5268. 'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),
  5269. 'src' => new HTMLPurifier_AttrDef_URI(true),
  5270. 'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript'))
  5271. );
  5272. $this->info['script']->content_model = '#PCDATA';
  5273. $this->info['script']->content_model_type = 'optional';
  5274. $this->info['script']->attr_transform_pre['type'] =
  5275. $this->info['script']->attr_transform_post['type'] =
  5276. new HTMLPurifier_AttrTransform_ScriptRequired();
  5277. }
  5278. }
  5279. class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
  5280. {
  5281. public $name = 'StyleAttribute';
  5282. public $attr_collections = array(
  5283. 'Style' => array('style' => false),
  5284. 'Core' => array(0 => array('Style'))
  5285. );
  5286. public function __construct() {
  5287. $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
  5288. }
  5289. }
  5290. class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
  5291. {
  5292. public $name = 'Tables';
  5293. public function __construct() {
  5294. $this->addElement('caption', false, 'Inline', 'Common');
  5295. $this->addElement('table', 'Block',
  5296. new HTMLPurifier_ChildDef_Table(), 'Common',
  5297. array(
  5298. 'border' => 'Pixels',
  5299. 'cellpadding' => 'Length',
  5300. 'cellspacing' => 'Length',
  5301. 'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
  5302. 'rules' => 'Enum#none,groups,rows,cols,all',
  5303. 'summary' => 'Text',
  5304. 'width' => 'Length'
  5305. )
  5306. );
  5307. $cell_align = array(
  5308. 'align' => 'Enum#left,center,right,justify,char',
  5309. 'charoff' => 'Length',
  5310. 'valign' => 'Enum#top,middle,bottom,baseline',
  5311. );
  5312. $cell_t = array_merge(
  5313. array(
  5314. 'abbr' => 'Text',
  5315. 'colspan' => 'Number',
  5316. 'rowspan' => 'Number',
  5317. ),
  5318. $cell_align
  5319. );
  5320. $this->addElement('td', false, 'Flow', 'Common', $cell_t);
  5321. $this->addElement('th', false, 'Flow', 'Common', $cell_t);
  5322. $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);
  5323. $cell_col = array_merge(
  5324. array(
  5325. 'span' => 'Number',
  5326. 'width' => 'MultiLength',
  5327. ),
  5328. $cell_align
  5329. );
  5330. $this->addElement('col', false, 'Empty', 'Common', $cell_col);
  5331. $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);
  5332. $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);
  5333. $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);
  5334. $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);
  5335. }
  5336. }
  5337. class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
  5338. {
  5339. public $name = 'Target';
  5340. public function __construct() {
  5341. $elements = array('a');
  5342. foreach ($elements as $name) {
  5343. $e = $this->addBlankElement($name);
  5344. $e->attr = array(
  5345. 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget()
  5346. );
  5347. }
  5348. }
  5349. }
  5350. class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
  5351. {
  5352. public $name = 'Text';
  5353. public $content_sets = array(
  5354. 'Flow' => 'Heading | Block | Inline'
  5355. );
  5356. public function __construct() {
  5357. $this->addElement('abbr', 'Inline', 'Inline', 'Common');
  5358. $this->addElement('acronym', 'Inline', 'Inline', 'Common');
  5359. $this->addElement('cite', 'Inline', 'Inline', 'Common');
  5360. $this->addElement('code', 'Inline', 'Inline', 'Common');
  5361. $this->addElement('dfn', 'Inline', 'Inline', 'Common');
  5362. $this->addElement('em', 'Inline', 'Inline', 'Common');
  5363. $this->addElement('kbd', 'Inline', 'Inline', 'Common');
  5364. $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI'));
  5365. $this->addElement('samp', 'Inline', 'Inline', 'Common');
  5366. $this->addElement('strong', 'Inline', 'Inline', 'Common');
  5367. $this->addElement('var', 'Inline', 'Inline', 'Common');
  5368. $this->addElement('span', 'Inline', 'Inline', 'Common');
  5369. $this->addElement('br', 'Inline', 'Empty', 'Core');
  5370. $this->addElement('address', 'Block', 'Inline', 'Common');
  5371. $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI') );
  5372. $pre = $this->addElement('pre', 'Block', 'Inline', 'Common');
  5373. $pre->excludes = $this->makeLookup(
  5374. 'img', 'big', 'small', 'object', 'applet', 'font', 'basefont' );
  5375. $this->addElement('h1', 'Heading', 'Inline', 'Common');
  5376. $this->addElement('h2', 'Heading', 'Inline', 'Common');
  5377. $this->addElement('h3', 'Heading', 'Inline', 'Common');
  5378. $this->addElement('h4', 'Heading', 'Inline', 'Common');
  5379. $this->addElement('h5', 'Heading', 'Inline', 'Common');
  5380. $this->addElement('h6', 'Heading', 'Inline', 'Common');
  5381. $this->addElement('p', 'Block', 'Inline', 'Common');
  5382. $this->addElement('div', 'Block', 'Flow', 'Common');
  5383. }
  5384. }
  5385. class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
  5386. {
  5387. public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
  5388. public $defaultLevel = null;
  5389. public $fixesForLevel = array(
  5390. 'light' => array(),
  5391. 'medium' => array(),
  5392. 'heavy' => array()
  5393. );
  5394. public function construct($config) {
  5395. $fixes = $this->makeFixes();
  5396. $this->makeFixesForLevel($fixes);
  5397. $level = $config->get('HTML', 'TidyLevel');
  5398. $fixes_lookup = $this->getFixesForLevel($level);
  5399. $add_fixes = $config->get('HTML', 'TidyAdd');
  5400. $remove_fixes = $config->get('HTML', 'TidyRemove');
  5401. foreach ($fixes as $name => $fix) {
  5402. if (
  5403. isset($remove_fixes[$name]) ||
  5404. (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))
  5405. ) {
  5406. unset($fixes[$name]);
  5407. }
  5408. }
  5409. $this->populate($fixes);
  5410. }
  5411. public function getFixesForLevel($level) {
  5412. if ($level == $this->levels[0]) {
  5413. return array();
  5414. }
  5415. $activated_levels = array();
  5416. for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
  5417. $activated_levels[] = $this->levels[$i];
  5418. if ($this->levels[$i] == $level) break;
  5419. }
  5420. if ($i == $c) {
  5421. trigger_error(
  5422. 'Tidy level ' . htmlspecialchars($level) . ' not recognized',
  5423. E_USER_WARNING
  5424. );
  5425. return array();
  5426. }
  5427. $ret = array();
  5428. foreach ($activated_levels as $level) {
  5429. foreach ($this->fixesForLevel[$level] as $fix) {
  5430. $ret[$fix] = true;
  5431. }
  5432. }
  5433. return $ret;
  5434. }
  5435. public function makeFixesForLevel($fixes) {
  5436. if (!isset($this->defaultLevel)) return;
  5437. if (!isset($this->fixesForLevel[$this->defaultLevel])) {
  5438. trigger_error(
  5439. 'Default level ' . $this->defaultLevel . ' does not exist',
  5440. E_USER_ERROR
  5441. );
  5442. return;
  5443. }
  5444. $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
  5445. }
  5446. public function populate($fixes) {
  5447. foreach ($fixes as $name => $fix) {
  5448. list($type, $params) = $this->getFixType($name);
  5449. switch ($type) {
  5450. case 'attr_transform_pre':
  5451. case 'attr_transform_post':
  5452. $attr = $params['attr'];
  5453. if (isset($params['element'])) {
  5454. $element = $params['element'];
  5455. if (empty($this->info[$element])) {
  5456. $e = $this->addBlankElement($element);
  5457. } else {
  5458. $e = $this->info[$element];
  5459. }
  5460. } else {
  5461. $type = "info_$type";
  5462. $e = $this;
  5463. }
  5464. $f =& $e->$type;
  5465. $f[$attr] = $fix;
  5466. break;
  5467. case 'tag_transform':
  5468. $this->info_tag_transform[$params['element']] = $fix;
  5469. break;
  5470. case 'child':
  5471. case 'content_model_type':
  5472. $element = $params['element'];
  5473. if (empty($this->info[$element])) {
  5474. $e = $this->addBlankElement($element);
  5475. } else {
  5476. $e = $this->info[$element];
  5477. }
  5478. $e->$type = $fix;
  5479. break;
  5480. default:
  5481. trigger_error("Fix type $type not supported", E_USER_ERROR);
  5482. break;
  5483. }
  5484. }
  5485. }
  5486. public function getFixType($name) {
  5487. $property = $attr = null;
  5488. if (strpos($name, '#') !== false) list($name, $property) = explode('#', $name);
  5489. if (strpos($name, '@') !== false) list($name, $attr) = explode('@', $name);
  5490. $params = array();
  5491. if ($name !== '') $params['element'] = $name;
  5492. if (!is_null($attr)) $params['attr'] = $attr;
  5493. if (!is_null($attr)) {
  5494. if (is_null($property)) $property = 'pre';
  5495. $type = 'attr_transform_' . $property;
  5496. return array($type, $params);
  5497. }
  5498. if (is_null($property)) {
  5499. return array('tag_transform', $params);
  5500. }
  5501. return array($property, $params);
  5502. }
  5503. public function makeFixes() {}
  5504. }
  5505. class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
  5506. {
  5507. public $name = 'XMLCommonAttributes';
  5508. public $attr_collections = array(
  5509. 'Lang' => array(
  5510. 'xml:lang' => 'LanguageCode',
  5511. )
  5512. );
  5513. }
  5514. class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
  5515. {
  5516. public $name = 'Tidy_Proprietary';
  5517. public $defaultLevel = 'light';
  5518. public function makeFixes() {
  5519. return array();
  5520. }
  5521. }
  5522. class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
  5523. {
  5524. public function makeFixes() {
  5525. $r = array();
  5526. $r['font'] = new HTMLPurifier_TagTransform_Font();
  5527. $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul');
  5528. $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul');
  5529. $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');
  5530. $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');
  5531. $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
  5532. $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
  5533. $r['caption@align'] =
  5534. new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
  5535. 'left' => 'text-align:left;',
  5536. 'right' => 'text-align:right;',
  5537. 'top' => 'caption-side:top;',
  5538. 'bottom' => 'caption-side:bottom;'
  5539. ));
  5540. $r['img@align'] =
  5541. new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
  5542. 'left' => 'float:left;',
  5543. 'right' => 'float:right;',
  5544. 'top' => 'vertical-align:top;',
  5545. 'middle' => 'vertical-align:middle;',
  5546. 'bottom' => 'vertical-align:baseline;',
  5547. ));
  5548. $r['table@align'] =
  5549. new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
  5550. 'left' => 'float:left;',
  5551. 'center' => 'margin-left:auto;margin-right:auto;',
  5552. 'right' => 'float:right;'
  5553. ));
  5554. $r['hr@align'] =
  5555. new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
  5556. 'left' => 'margin-left:0;margin-right:auto;text-align:left;',
  5557. 'center' => 'margin-left:auto;margin-right:auto;text-align:center;',
  5558. 'right' => 'margin-left:auto;margin-right:0;text-align:right;'
  5559. ));
  5560. $align_lookup = array();
  5561. $align_values = array('left', 'right', 'center', 'justify');
  5562. foreach ($align_values as $v) $align_lookup[$v] = "text-align:$v;";
  5563. $r['h1@align'] =
  5564. $r['h2@align'] =
  5565. $r['h3@align'] =
  5566. $r['h4@align'] =
  5567. $r['h5@align'] =
  5568. $r['h6@align'] =
  5569. $r['p@align'] =
  5570. $r['div@align'] =
  5571. new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
  5572. $r['table@bgcolor'] =
  5573. $r['td@bgcolor'] =
  5574. $r['th@bgcolor'] =
  5575. new HTMLPurifier_AttrTransform_BgColor();
  5576. $r['img@border'] = new HTMLPurifier_AttrTransform_Border();
  5577. $r['br@clear'] =
  5578. new HTMLPurifier_AttrTransform_EnumToCSS('clear', array(
  5579. 'left' => 'clear:left;',
  5580. 'right' => 'clear:right;',
  5581. 'all' => 'clear:both;',
  5582. 'none' => 'clear:none;',
  5583. ));
  5584. $r['td@height'] =
  5585. $r['th@height'] =
  5586. new HTMLPurifier_AttrTransform_Length('height');
  5587. $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
  5588. $r['img@name'] =
  5589. $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
  5590. $r['hr@noshade'] =
  5591. new HTMLPurifier_AttrTransform_BoolToCSS(
  5592. 'noshade',
  5593. 'color:#808080;background-color:#808080;border:0;'
  5594. );
  5595. $r['td@nowrap'] =
  5596. $r['th@nowrap'] =
  5597. new HTMLPurifier_AttrTransform_BoolToCSS(
  5598. 'nowrap',
  5599. 'white-space:nowrap;'
  5600. );
  5601. $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');
  5602. $ul_types = array(
  5603. 'disc' => 'list-style-type:disc;',
  5604. 'square' => 'list-style-type:square;',
  5605. 'circle' => 'list-style-type:circle;'
  5606. );
  5607. $ol_types = array(
  5608. '1' => 'list-style-type:decimal;',
  5609. 'i' => 'list-style-type:lower-roman;',
  5610. 'I' => 'list-style-type:upper-roman;',
  5611. 'a' => 'list-style-type:lower-alpha;',
  5612. 'A' => 'list-style-type:upper-alpha;'
  5613. );
  5614. $li_types = $ul_types + $ol_types;
  5615. $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
  5616. $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
  5617. $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
  5618. $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
  5619. $r['td@width'] =
  5620. $r['th@width'] =
  5621. $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
  5622. return $r;
  5623. }
  5624. }
  5625. class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
  5626. {
  5627. public $name = 'Tidy_Strict';
  5628. public $defaultLevel = 'light';
  5629. public function makeFixes() {
  5630. $r = parent::makeFixes();
  5631. $r['blockquote#content_model_type'] = 'strictblockquote';
  5632. return $r;
  5633. }
  5634. public $defines_child_def = true;
  5635. public function getChildDef($def) {
  5636. if ($def->content_model_type != 'strictblockquote') return parent::getChildDef($def);
  5637. return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
  5638. }
  5639. }
  5640. class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
  5641. {
  5642. public $name = 'Tidy_Transitional';
  5643. public $defaultLevel = 'heavy';
  5644. }
  5645. class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
  5646. {
  5647. public $name = 'Tidy_XHTML';
  5648. public $defaultLevel = 'medium';
  5649. public function makeFixes() {
  5650. $r = array();
  5651. $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
  5652. return $r;
  5653. }
  5654. }
  5655. class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
  5656. {
  5657. public $name = 'AutoParagraph';
  5658. public $needed = array('p');
  5659. private function _pStart() {
  5660. $par = new HTMLPurifier_Token_Start('p');
  5661. $par->armor['MakeWellFormed_TagClosedError'] = true;
  5662. return $par;
  5663. }
  5664. public function handleText(&$token) {
  5665. $text = $token->data;
  5666. if (empty($this->currentNesting)) {
  5667. if (!$this->allowsElement('p')) return;
  5668. $token = array($this->_pStart());
  5669. $this->_splitText($text, $token);
  5670. } elseif ($this->currentNesting[count($this->currentNesting)-1]->name == 'p') {
  5671. $token = array();
  5672. $this->_splitText($text, $token);
  5673. } elseif ($this->allowsElement('p')) {
  5674. if (strpos($text, "\n\n") !== false) {
  5675. $token = array($this->_pStart());
  5676. $this->_splitText($text, $token);
  5677. } else {
  5678. $ok = false;
  5679. $nesting = 0;
  5680. for ($i = $this->inputIndex + 1; isset($this->inputTokens[$i]); $i++) {
  5681. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_Start){
  5682. if (!$this->_isInline($this->inputTokens[$i])) {
  5683. $ok = false;
  5684. break;
  5685. }
  5686. $nesting++;
  5687. }
  5688. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_End) {
  5689. if ($nesting <= 0) break;
  5690. $nesting--;
  5691. }
  5692. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_Text) {
  5693. if (strpos($this->inputTokens[$i]->data, "\n\n") !== false) {
  5694. $ok = true;
  5695. break;
  5696. }
  5697. }
  5698. }
  5699. if ($ok) {
  5700. $token = array($this->_pStart(), $token);
  5701. }
  5702. }
  5703. }
  5704. }
  5705. public function handleElement(&$token) {
  5706. if (!empty($this->currentNesting)) {
  5707. if ($this->allowsElement('p')) {
  5708. if ($token->name == 'p') return;
  5709. if (!$this->_isInline($token)) return;
  5710. $prev = $this->inputTokens[$this->inputIndex - 1];
  5711. if (!$prev instanceof HTMLPurifier_Token_Start) {
  5712. if (
  5713. $prev->name == 'p' && $prev instanceof HTMLPurifier_Token_End &&
  5714. $this->_isInline($token)
  5715. ) {
  5716. $token = array($this->_pStart(), $token);
  5717. }
  5718. return;
  5719. }
  5720. $ok = false;
  5721. $j = 1;
  5722. for ($i = $this->inputIndex; isset($this->inputTokens[$i]); $i++) {
  5723. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_Start) $j++;
  5724. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_End) $j--;
  5725. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_Text) {
  5726. if (strpos($this->inputTokens[$i]->data, "\n\n") !== false) {
  5727. $ok = true;
  5728. break;
  5729. }
  5730. }
  5731. if ($j <= 0) break;
  5732. }
  5733. if ($ok) {
  5734. $token = array($this->_pStart(), $token);
  5735. }
  5736. }
  5737. return;
  5738. }
  5739. if (!$this->_isInline($token)) return;
  5740. $token = array($this->_pStart(), $token);
  5741. }
  5742. private function _splitText($data, &$result) {
  5743. $raw_paragraphs = explode("\n\n", $data);
  5744. $paragraphs = array();
  5745. $needs_start = false;
  5746. $needs_end = false;
  5747. $c = count($raw_paragraphs);
  5748. if ($c == 1) {
  5749. $result[] = new HTMLPurifier_Token_Text($data);
  5750. return;
  5751. }
  5752. for ($i = 0; $i < $c; $i++) {
  5753. $par = $raw_paragraphs[$i];
  5754. if (trim($par) !== '') {
  5755. $paragraphs[] = $par;
  5756. continue;
  5757. }
  5758. if ($i == 0 && empty($result)) {
  5759. $result[] = new HTMLPurifier_Token_End('p');
  5760. $needs_start = true;
  5761. } elseif ($i + 1 == $c) {
  5762. $needs_end = true;
  5763. }
  5764. }
  5765. if (empty($paragraphs)) {
  5766. return;
  5767. }
  5768. if ($needs_start) $result[] = $this->_pStart();
  5769. foreach ($paragraphs as $par) {
  5770. $result[] = new HTMLPurifier_Token_Text($par);
  5771. $result[] = new HTMLPurifier_Token_End('p');
  5772. $result[] = $this->_pStart();
  5773. }
  5774. array_pop($result);
  5775. $remove_paragraph_end = true;
  5776. if (!$needs_end) {
  5777. for ($i = $this->inputIndex + 1; isset($this->inputTokens[$i]); $i++) {
  5778. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_Start || $this->inputTokens[$i] instanceof HTMLPurifier_Token_Empty) {
  5779. $remove_paragraph_end = $this->_isInline($this->inputTokens[$i]);
  5780. }
  5781. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_Text && !$this->inputTokens[$i]->is_whitespace) break;
  5782. if ($this->inputTokens[$i] instanceof HTMLPurifier_Token_End) break;
  5783. }
  5784. } else {
  5785. $remove_paragraph_end = false;
  5786. }
  5787. if ($remove_paragraph_end) {
  5788. array_pop($result);
  5789. }
  5790. }
  5791. private function _isInline($token) {
  5792. return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);
  5793. }
  5794. }
  5795. class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
  5796. {
  5797. public $name = 'Linkify';
  5798. public $needed = array('a' => array('href'));
  5799. public function handleText(&$token) {
  5800. if (!$this->allowsElement('a')) return;
  5801. if (strpos($token->data, '://') === false) {
  5802. return;
  5803. }
  5804. $bits = preg_split('#((?:https?|ftp)://[^\s\'"<>()]+)#S', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
  5805. $token = array();
  5806. for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
  5807. if (!$l) {
  5808. if ($bits[$i] === '') continue;
  5809. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  5810. } else {
  5811. $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));
  5812. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  5813. $token[] = new HTMLPurifier_Token_End('a');
  5814. }
  5815. }
  5816. }
  5817. }
  5818. class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
  5819. {
  5820. public $name = 'PurifierLinkify';
  5821. public $docURL;
  5822. public $needed = array('a' => array('href'));
  5823. public function prepare($config, $context) {
  5824. $this->docURL = $config->get('AutoFormatParam', 'PurifierLinkifyDocURL');
  5825. return parent::prepare($config, $context);
  5826. }
  5827. public function handleText(&$token) {
  5828. if (!$this->allowsElement('a')) return;
  5829. if (strpos($token->data, '%') === false) return;
  5830. $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
  5831. $token = array();
  5832. for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
  5833. if (!$l) {
  5834. if ($bits[$i] === '') continue;
  5835. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  5836. } else {
  5837. $token[] = new HTMLPurifier_Token_Start('a',
  5838. array('href' => str_replace('%s', $bits[$i], $this->docURL)));
  5839. $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);
  5840. $token[] = new HTMLPurifier_Token_End('a');
  5841. }
  5842. }
  5843. }
  5844. }
  5845. class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
  5846. {
  5847. private $factory;
  5848. public function __construct() {
  5849. parent::__construct();
  5850. $this->factory = new HTMLPurifier_TokenFactory();
  5851. }
  5852. public function tokenizeHTML($html, $config, $context) {
  5853. $html = $this->normalize($html, $config, $context);
  5854. if ($config->get('Core', 'AggressivelyFixLt')) {
  5855. $char = '[^a-z!\/]';
  5856. $comment = "/<!--(.*?)(-->|\z)/is";
  5857. $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
  5858. $html = preg_replace("/<($char)/i", '&lt;\\1', $html);
  5859. $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html);
  5860. }
  5861. $html = $this->wrapHTML($html, $config, $context);
  5862. $doc = new DOMDocument();
  5863. $doc->encoding = 'UTF-8';
  5864. set_error_handler(array($this, 'muteErrorHandler'));
  5865. $doc->loadHTML($html);
  5866. restore_error_handler();
  5867. $tokens = array();
  5868. $this->tokenizeDOM(
  5869. $doc->getElementsByTagName('html')->item(0)->
  5870. getElementsByTagName('body')->item(0)->
  5871. getElementsByTagName('div')->item(0)
  5872. , $tokens);
  5873. return $tokens;
  5874. }
  5875. protected function tokenizeDOM($node, &$tokens, $collect = false) {
  5876. if ($node->nodeType === XML_TEXT_NODE) {
  5877. $tokens[] = $this->factory->createText($node->data);
  5878. return;
  5879. } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
  5880. $last = end($tokens);
  5881. $data = $node->data;
  5882. if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
  5883. $new_data = trim($data);
  5884. if (substr($new_data, 0, 4) === '<!--') {
  5885. $data = substr($new_data, 4);
  5886. if (substr($data, -3) === '-->') {
  5887. $data = substr($data, 0, -3);
  5888. } else {
  5889. }
  5890. }
  5891. }
  5892. $tokens[] = $this->factory->createText($this->parseData($data));
  5893. return;
  5894. } elseif ($node->nodeType === XML_COMMENT_NODE) {
  5895. $tokens[] = $this->factory->createComment($node->data);
  5896. return;
  5897. } elseif (
  5898. $node->nodeType !== XML_ELEMENT_NODE
  5899. ) {
  5900. return;
  5901. }
  5902. $attr = $node->hasAttributes() ?
  5903. $this->transformAttrToAssoc($node->attributes) :
  5904. array();
  5905. if (!$node->childNodes->length) {
  5906. if ($collect) {
  5907. $tokens[] = $this->factory->createEmpty($node->tagName, $attr);
  5908. }
  5909. } else {
  5910. if ($collect) {
  5911. $tokens[] = $this->factory->createStart(
  5912. $tag_name = $node->tagName,
  5913. $attr
  5914. );
  5915. }
  5916. foreach ($node->childNodes as $node) {
  5917. $this->tokenizeDOM($node, $tokens, true);
  5918. }
  5919. if ($collect) {
  5920. $tokens[] = $this->factory->createEnd($tag_name);
  5921. }
  5922. }
  5923. }
  5924. protected function transformAttrToAssoc($node_map) {
  5925. if ($node_map->length === 0) return array();
  5926. $array = array();
  5927. foreach ($node_map as $attr) {
  5928. $array[$attr->name] = $attr->value;
  5929. }
  5930. return $array;
  5931. }
  5932. public function muteErrorHandler($errno, $errstr) {}
  5933. public function callbackUndoCommentSubst($matches) {
  5934. return '<!--' . strtr($matches[1], array('&amp;'=>'&','&lt;'=>'<')) . $matches[2];
  5935. }
  5936. public function callbackArmorCommentEntities($matches) {
  5937. return '<!--' . str_replace('&', '&amp;', $matches[1]) . $matches[2];
  5938. }
  5939. protected function wrapHTML($html, $config, $context) {
  5940. $def = $config->getDefinition('HTML');
  5941. $ret = '';
  5942. if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
  5943. $ret .= '<!DOCTYPE html ';
  5944. if (!empty($def->doctype->dtdPublic)) $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
  5945. if (!empty($def->doctype->dtdSystem)) $ret .= '"' . $def->doctype->dtdSystem . '" ';
  5946. $ret .= '>';
  5947. }
  5948. $ret .= '<html><head>';
  5949. $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
  5950. $ret .= '</head><body><div>'.$html.'</div></body></html>';
  5951. return $ret;
  5952. }
  5953. }
  5954. class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
  5955. {
  5956. protected $_whitespace = "\x20\x09\x0D\x0A";
  5957. protected function scriptCallback($matches) {
  5958. return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
  5959. }
  5960. public function tokenizeHTML($html, $config, $context) {
  5961. if ($config->get('HTML', 'Trusted')) {
  5962. $html = preg_replace_callback('#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
  5963. array($this, 'scriptCallback'), $html);
  5964. }
  5965. $html = $this->normalize($html, $config, $context);
  5966. $cursor = 0;
  5967. $inside_tag = false;
  5968. $array = array();
  5969. $maintain_line_numbers = $config->get('Core', 'MaintainLineNumbers');
  5970. if ($maintain_line_numbers === null) {
  5971. $maintain_line_numbers = $config->get('Core', 'CollectErrors');
  5972. }
  5973. if ($maintain_line_numbers) $current_line = 1;
  5974. else $current_line = false;
  5975. $context->register('CurrentLine', $current_line);
  5976. $nl = "\n";
  5977. $synchronize_interval = $config->get('Core', 'DirectLexLineNumberSyncInterval');
  5978. $e = false;
  5979. if ($config->get('Core', 'CollectErrors')) {
  5980. $e =& $context->get('ErrorCollector');
  5981. }
  5982. $loops = 0;
  5983. while(++$loops) {
  5984. if (
  5985. $maintain_line_numbers &&
  5986. $synchronize_interval &&
  5987. $cursor > 0 &&
  5988. $loops % $synchronize_interval === 0
  5989. ) {
  5990. $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
  5991. }
  5992. $position_next_lt = strpos($html, '<', $cursor);
  5993. $position_next_gt = strpos($html, '>', $cursor);
  5994. if ($position_next_lt === $cursor) {
  5995. $inside_tag = true;
  5996. $cursor++;
  5997. }
  5998. if (!$inside_tag && $position_next_lt !== false) {
  5999. $token = new
  6000. HTMLPurifier_Token_Text(
  6001. $this->parseData(
  6002. substr(
  6003. $html, $cursor, $position_next_lt - $cursor
  6004. )
  6005. )
  6006. );
  6007. if ($maintain_line_numbers) {
  6008. $token->line = $current_line;
  6009. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
  6010. }
  6011. $array[] = $token;
  6012. $cursor = $position_next_lt + 1;
  6013. $inside_tag = true;
  6014. continue;
  6015. } elseif (!$inside_tag) {
  6016. if ($cursor === strlen($html)) break;
  6017. $token = new
  6018. HTMLPurifier_Token_Text(
  6019. $this->parseData(
  6020. substr(
  6021. $html, $cursor
  6022. )
  6023. )
  6024. );
  6025. if ($maintain_line_numbers) $token->line = $current_line;
  6026. $array[] = $token;
  6027. break;
  6028. } elseif ($inside_tag && $position_next_gt !== false) {
  6029. $strlen_segment = $position_next_gt - $cursor;
  6030. if ($strlen_segment < 1) {
  6031. $token = new HTMLPurifier_Token_Text('<');
  6032. $cursor++;
  6033. continue;
  6034. }
  6035. $segment = substr($html, $cursor, $strlen_segment);
  6036. if ($segment === false) {
  6037. break;
  6038. }
  6039. if (
  6040. substr($segment, 0, 3) === '!--'
  6041. ) {
  6042. $position_comment_end = strpos($html, '-->', $cursor);
  6043. if ($position_comment_end === false) {
  6044. if ($e) $e->send(E_WARNING, 'Lexer: Unclosed comment');
  6045. $position_comment_end = strlen($html);
  6046. $end = true;
  6047. } else {
  6048. $end = false;
  6049. }
  6050. $strlen_segment = $position_comment_end - $cursor;
  6051. $segment = substr($html, $cursor, $strlen_segment);
  6052. $token = new
  6053. HTMLPurifier_Token_Comment(
  6054. substr(
  6055. $segment, 3, $strlen_segment - 3
  6056. )
  6057. );
  6058. if ($maintain_line_numbers) {
  6059. $token->line = $current_line;
  6060. $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
  6061. }
  6062. $array[] = $token;
  6063. $cursor = $end ? $position_comment_end : $position_comment_end + 3;
  6064. $inside_tag = false;
  6065. continue;
  6066. }
  6067. $is_end_tag = (strpos($segment,'/') === 0);
  6068. if ($is_end_tag) {
  6069. $type = substr($segment, 1);
  6070. $token = new HTMLPurifier_Token_End($type);
  6071. if ($maintain_line_numbers) {
  6072. $token->line = $current_line;
  6073. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  6074. }
  6075. $array[] = $token;
  6076. $inside_tag = false;
  6077. $cursor = $position_next_gt + 1;
  6078. continue;
  6079. }
  6080. if (!ctype_alpha($segment[0])) {
  6081. if ($e) $e->send(E_NOTICE, 'Lexer: Unescaped lt');
  6082. $token = new
  6083. HTMLPurifier_Token_Text(
  6084. '<' .
  6085. $this->parseData(
  6086. $segment
  6087. ) .
  6088. '>'
  6089. );
  6090. if ($maintain_line_numbers) {
  6091. $token->line = $current_line;
  6092. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  6093. }
  6094. $array[] = $token;
  6095. $cursor = $position_next_gt + 1;
  6096. $inside_tag = false;
  6097. continue;
  6098. }
  6099. $is_self_closing = (strrpos($segment,'/') === $strlen_segment-1);
  6100. if ($is_self_closing) {
  6101. $strlen_segment--;
  6102. $segment = substr($segment, 0, $strlen_segment);
  6103. }
  6104. $position_first_space = strcspn($segment, $this->_whitespace);
  6105. if ($position_first_space >= $strlen_segment) {
  6106. if ($is_self_closing) {
  6107. $token = new HTMLPurifier_Token_Empty($segment);
  6108. } else {
  6109. $token = new HTMLPurifier_Token_Start($segment);
  6110. }
  6111. if ($maintain_line_numbers) {
  6112. $token->line = $current_line;
  6113. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  6114. }
  6115. $array[] = $token;
  6116. $inside_tag = false;
  6117. $cursor = $position_next_gt + 1;
  6118. continue;
  6119. }
  6120. $type = substr($segment, 0, $position_first_space);
  6121. $attribute_string =
  6122. trim(
  6123. substr(
  6124. $segment, $position_first_space
  6125. )
  6126. );
  6127. if ($attribute_string) {
  6128. $attr = $this->parseAttributeString(
  6129. $attribute_string
  6130. , $config, $context
  6131. );
  6132. } else {
  6133. $attr = array();
  6134. }
  6135. if ($is_self_closing) {
  6136. $token = new HTMLPurifier_Token_Empty($type, $attr);
  6137. } else {
  6138. $token = new HTMLPurifier_Token_Start($type, $attr);
  6139. }
  6140. if ($maintain_line_numbers) {
  6141. $token->line = $current_line;
  6142. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  6143. }
  6144. $array[] = $token;
  6145. $cursor = $position_next_gt + 1;
  6146. $inside_tag = false;
  6147. continue;
  6148. } else {
  6149. if ($e) $e->send(E_WARNING, 'Lexer: Missing gt');
  6150. $token = new
  6151. HTMLPurifier_Token_Text(
  6152. '<' .
  6153. $this->parseData(
  6154. substr($html, $cursor)
  6155. )
  6156. );
  6157. if ($maintain_line_numbers) $token->line = $current_line;
  6158. $array[] = $token;
  6159. break;
  6160. }
  6161. break;
  6162. }
  6163. $context->destroy('CurrentLine');
  6164. return $array;
  6165. }
  6166. protected function substrCount($haystack, $needle, $offset, $length) {
  6167. static $oldVersion;
  6168. if ($oldVersion === null) {
  6169. $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
  6170. }
  6171. if ($oldVersion) {
  6172. $haystack = substr($haystack, $offset, $length);
  6173. return substr_count($haystack, $needle);
  6174. } else {
  6175. return substr_count($haystack, $needle, $offset, $length);
  6176. }
  6177. }
  6178. public function parseAttributeString($string, $config, $context) {
  6179. $string = (string) $string;
  6180. if ($string == '') return array();
  6181. $e = false;
  6182. if ($config->get('Core', 'CollectErrors')) {
  6183. $e =& $context->get('ErrorCollector');
  6184. }
  6185. $num_equal = substr_count($string, '=');
  6186. $has_space = strpos($string, ' ');
  6187. if ($num_equal === 0 && !$has_space) {
  6188. return array($string => $string);
  6189. } elseif ($num_equal === 1 && !$has_space) {
  6190. list($key, $quoted_value) = explode('=', $string);
  6191. $quoted_value = trim($quoted_value);
  6192. if (!$key) {
  6193. if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
  6194. return array();
  6195. }
  6196. if (!$quoted_value) return array($key => '');
  6197. $first_char = @$quoted_value[0];
  6198. $last_char = @$quoted_value[strlen($quoted_value)-1];
  6199. $same_quote = ($first_char == $last_char);
  6200. $open_quote = ($first_char == '"' || $first_char == "'");
  6201. if ( $same_quote && $open_quote) {
  6202. $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
  6203. } else {
  6204. if ($open_quote) {
  6205. if ($e) $e->send(E_ERROR, 'Lexer: Missing end quote');
  6206. $value = substr($quoted_value, 1);
  6207. } else {
  6208. $value = $quoted_value;
  6209. }
  6210. }
  6211. if ($value === false) $value = '';
  6212. return array($key => $value);
  6213. }
  6214. $array = array();
  6215. $cursor = 0;
  6216. $size = strlen($string);
  6217. $string .= ' ';
  6218. while(true) {
  6219. if ($cursor >= $size) {
  6220. break;
  6221. }
  6222. $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
  6223. $key_begin = $cursor;
  6224. $cursor += strcspn($string, $this->_whitespace . '=', $cursor);
  6225. $key_end = $cursor;
  6226. $key = substr($string, $key_begin, $key_end - $key_begin);
  6227. if (!$key) {
  6228. if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
  6229. $cursor += strcspn($string, $this->_whitespace, $cursor + 1);
  6230. continue;
  6231. }
  6232. $cursor += strspn($string, $this->_whitespace, $cursor);
  6233. if ($cursor >= $size) {
  6234. $array[$key] = $key;
  6235. break;
  6236. }
  6237. $first_char = @$string[$cursor];
  6238. if ($first_char == '=') {
  6239. $cursor++;
  6240. $cursor += strspn($string, $this->_whitespace, $cursor);
  6241. if ($cursor === false) {
  6242. $array[$key] = '';
  6243. break;
  6244. }
  6245. $char = @$string[$cursor];
  6246. if ($char == '"' || $char == "'") {
  6247. $cursor++;
  6248. $value_begin = $cursor;
  6249. $cursor = strpos($string, $char, $cursor);
  6250. $value_end = $cursor;
  6251. } else {
  6252. $value_begin = $cursor;
  6253. $cursor += strcspn($string, $this->_whitespace, $cursor);
  6254. $value_end = $cursor;
  6255. }
  6256. if ($cursor === false) {
  6257. $cursor = $size;
  6258. $value_end = $cursor;
  6259. }
  6260. $value = substr($string, $value_begin, $value_end - $value_begin);
  6261. if ($value === false) $value = '';
  6262. $array[$key] = $this->parseData($value);
  6263. $cursor++;
  6264. } else {
  6265. if ($key !== '') {
  6266. $array[$key] = $key;
  6267. } else {
  6268. if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
  6269. }
  6270. }
  6271. }
  6272. return $array;
  6273. }
  6274. }
  6275. abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
  6276. {
  6277. protected $strategies = array();
  6278. abstract public function __construct();
  6279. public function execute($tokens, $config, $context) {
  6280. foreach ($this->strategies as $strategy) {
  6281. $tokens = $strategy->execute($tokens, $config, $context);
  6282. }
  6283. return $tokens;
  6284. }
  6285. }
  6286. class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
  6287. {
  6288. public function __construct() {
  6289. $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
  6290. $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
  6291. $this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
  6292. $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
  6293. }
  6294. }
  6295. class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
  6296. {
  6297. public function execute($tokens, $config, $context) {
  6298. //####################################################################//
  6299. $definition = $config->getHTMLDefinition();
  6300. $parent_name = $definition->info_parent;
  6301. array_unshift($tokens, new HTMLPurifier_Token_Start($parent_name));
  6302. $tokens[] = new HTMLPurifier_Token_End($parent_name);
  6303. $is_inline = $definition->info_parent_def->descendants_are_inline;
  6304. $context->register('IsInline', $is_inline);
  6305. $e =& $context->get('ErrorCollector', true);
  6306. //####################################################################//
  6307. $stack = array();
  6308. $exclude_stack = array();
  6309. $start_token = false;
  6310. $context->register('CurrentToken', $start_token);
  6311. //####################################################################//
  6312. for ($i = 0, $size = count($tokens) ; $i < $size; ) {
  6313. $child_tokens = array();
  6314. for ($j = $i, $depth = 0; ; $j++) {
  6315. if ($tokens[$j] instanceof HTMLPurifier_Token_Start) {
  6316. $depth++;
  6317. if ($depth == 1) continue;
  6318. } elseif ($tokens[$j] instanceof HTMLPurifier_Token_End) {
  6319. $depth--;
  6320. if ($depth == 0) break;
  6321. }
  6322. $child_tokens[] = $tokens[$j];
  6323. }
  6324. $start_token = $tokens[$i];
  6325. if ($count = count($stack)) {
  6326. $parent_index = $stack[$count-1];
  6327. $parent_name = $tokens[$parent_index]->name;
  6328. if ($parent_index == 0) {
  6329. $parent_def = $definition->info_parent_def;
  6330. } else {
  6331. $parent_def = $definition->info[$parent_name];
  6332. }
  6333. } else {
  6334. $parent_index = $parent_name = $parent_def = null;
  6335. }
  6336. if ($is_inline === false) {
  6337. if (!empty($parent_def) && $parent_def->descendants_are_inline) {
  6338. $is_inline = $count - 1;
  6339. }
  6340. } else {
  6341. if ($count === $is_inline) {
  6342. $is_inline = false;
  6343. }
  6344. }
  6345. $excluded = false;
  6346. if (!empty($exclude_stack)) {
  6347. foreach ($exclude_stack as $lookup) {
  6348. if (isset($lookup[$tokens[$i]->name])) {
  6349. $excluded = true;
  6350. break;
  6351. }
  6352. }
  6353. }
  6354. if ($excluded) {
  6355. $result = false;
  6356. $excludes = array();
  6357. } else {
  6358. if ($i === 0) {
  6359. $def = $definition->info_parent_def;
  6360. } else {
  6361. $def = $definition->info[$tokens[$i]->name];
  6362. }
  6363. if (!empty($def->child)) {
  6364. $result = $def->child->validateChildren(
  6365. $child_tokens, $config, $context);
  6366. } else {
  6367. $result = false;
  6368. }
  6369. $excludes = $def->excludes;
  6370. }
  6371. if ($result === true || $child_tokens === $result) {
  6372. $stack[] = $i;
  6373. if (!empty($excludes)) $exclude_stack[] = $excludes;
  6374. $i++;
  6375. } elseif($result === false) {
  6376. if ($e) {
  6377. if ($excluded) {
  6378. $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
  6379. } else {
  6380. $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
  6381. }
  6382. }
  6383. $length = $j - $i + 1;
  6384. array_splice($tokens, $i, $length);
  6385. $size -= $length;
  6386. if (!$parent_def->child->allow_empty) {
  6387. $i = $parent_index;
  6388. array_pop($stack);
  6389. }
  6390. } else {
  6391. $length = $j - $i - 1;
  6392. if ($e) {
  6393. if (empty($result) && $length) {
  6394. $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
  6395. } else {
  6396. $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
  6397. }
  6398. }
  6399. array_splice($tokens, $i + 1, $length, $result);
  6400. $size -= $length;
  6401. $size += count($result);
  6402. $stack[] = $i;
  6403. if (!empty($excludes)) $exclude_stack[] = $excludes;
  6404. $i++;
  6405. }
  6406. $size = count($tokens);
  6407. while ($i < $size and !$tokens[$i] instanceof HTMLPurifier_Token_Start) {
  6408. if ($tokens[$i] instanceof HTMLPurifier_Token_End) {
  6409. array_pop($stack);
  6410. if ($i == 0 || $i == $size - 1) {
  6411. $s_excludes = $definition->info_parent_def->excludes;
  6412. } else {
  6413. $s_excludes = $definition->info[$tokens[$i]->name]->excludes;
  6414. }
  6415. if ($s_excludes) {
  6416. array_pop($exclude_stack);
  6417. }
  6418. }
  6419. $i++;
  6420. }
  6421. }
  6422. //####################################################################//
  6423. array_shift($tokens);
  6424. array_pop($tokens);
  6425. $context->destroy('IsInline');
  6426. $context->destroy('CurrentToken');
  6427. //####################################################################//
  6428. return $tokens;
  6429. }
  6430. }
  6431. class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
  6432. {
  6433. protected $inputTokens, $inputIndex, $outputTokens, $currentNesting,
  6434. $currentInjector, $injectors;
  6435. public function execute($tokens, $config, $context) {
  6436. $definition = $config->getHTMLDefinition();
  6437. $result = array();
  6438. $generator = new HTMLPurifier_Generator();
  6439. $escape_invalid_tags = $config->get('Core', 'EscapeInvalidTags');
  6440. $e = $context->get('ErrorCollector', true);
  6441. $this->currentNesting = array();
  6442. $this->inputIndex = false;
  6443. $this->inputTokens =& $tokens;
  6444. $this->outputTokens =& $result;
  6445. $context->register('CurrentNesting', $this->currentNesting);
  6446. $context->register('InputIndex', $this->inputIndex);
  6447. $context->register('InputTokens', $tokens);
  6448. $this->injectors = array();
  6449. $injectors = $config->getBatch('AutoFormat');
  6450. $custom_injectors = $injectors['Custom'];
  6451. unset($injectors['Custom']);
  6452. foreach ($injectors as $injector => $b) {
  6453. $injector = "HTMLPurifier_Injector_$injector";
  6454. if (!$b) continue;
  6455. $this->injectors[] = new $injector;
  6456. }
  6457. foreach ($custom_injectors as $injector) {
  6458. if (is_string($injector)) {
  6459. $injector = "HTMLPurifier_Injector_$injector";
  6460. $injector = new $injector;
  6461. }
  6462. $this->injectors[] = $injector;
  6463. }
  6464. $this->currentInjector = false;
  6465. foreach ($this->injectors as $i => $injector) {
  6466. $error = $injector->prepare($config, $context);
  6467. if (!$error) continue;
  6468. array_splice($this->injectors, $i, 1);
  6469. trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
  6470. }
  6471. $token = false;
  6472. $context->register('CurrentToken', $token);
  6473. for ($this->inputIndex = 0; isset($tokens[$this->inputIndex]); $this->inputIndex++) {
  6474. $token = $tokens[$this->inputIndex];
  6475. foreach ($this->injectors as $injector) {
  6476. if ($injector->skip > 0) $injector->skip--;
  6477. }
  6478. if (empty( $token->is_tag )) {
  6479. if ($token instanceof HTMLPurifier_Token_Text) {
  6480. foreach ($this->injectors as $i => $injector) {
  6481. if (!$injector->skip) $injector->handleText($token);
  6482. if (is_array($token)) {
  6483. $this->currentInjector = $i;
  6484. break;
  6485. }
  6486. }
  6487. }
  6488. $this->processToken($token, $config, $context);
  6489. continue;
  6490. }
  6491. $info = $definition->info[$token->name]->child;
  6492. $ok = false;
  6493. if ($info->type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
  6494. $token = new HTMLPurifier_Token_Empty($token->name, $token->attr);
  6495. $ok = true;
  6496. } elseif ($info->type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
  6497. $token = array(
  6498. new HTMLPurifier_Token_Start($token->name, $token->attr),
  6499. new HTMLPurifier_Token_End($token->name)
  6500. );
  6501. $ok = true;
  6502. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  6503. $ok = true;
  6504. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  6505. if (!empty($this->currentNesting)) {
  6506. $parent = array_pop($this->currentNesting);
  6507. $parent_info = $definition->info[$parent->name];
  6508. if (!isset($parent_info->child->elements[$token->name])) {
  6509. if ($e) $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);
  6510. $result[] = new HTMLPurifier_Token_End($parent->name);
  6511. $this->inputIndex--;
  6512. continue;
  6513. }
  6514. $this->currentNesting[] = $parent;
  6515. }
  6516. $ok = true;
  6517. }
  6518. if ($ok) {
  6519. foreach ($this->injectors as $i => $injector) {
  6520. if (!$injector->skip) $injector->handleElement($token);
  6521. if (is_array($token)) {
  6522. $this->currentInjector = $i;
  6523. break;
  6524. }
  6525. }
  6526. $this->processToken($token, $config, $context);
  6527. continue;
  6528. }
  6529. if (!$token instanceof HTMLPurifier_Token_End) continue;
  6530. if (empty($this->currentNesting)) {
  6531. if ($escape_invalid_tags) {
  6532. if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
  6533. $result[] = new HTMLPurifier_Token_Text(
  6534. $generator->generateFromToken($token, $config, $context)
  6535. );
  6536. } elseif ($e) {
  6537. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
  6538. }
  6539. continue;
  6540. }
  6541. $current_parent = array_pop($this->currentNesting);
  6542. if ($current_parent->name == $token->name) {
  6543. $result[] = $token;
  6544. foreach ($this->injectors as $i => $injector) {
  6545. $injector->notifyEnd($token);
  6546. }
  6547. continue;
  6548. }
  6549. $this->currentNesting[] = $current_parent;
  6550. $size = count($this->currentNesting);
  6551. $skipped_tags = false;
  6552. for ($i = $size - 2; $i >= 0; $i--) {
  6553. if ($this->currentNesting[$i]->name == $token->name) {
  6554. $skipped_tags = array_splice($this->currentNesting, $i);
  6555. break;
  6556. }
  6557. }
  6558. if ($skipped_tags === false) {
  6559. if ($escape_invalid_tags) {
  6560. $result[] = new HTMLPurifier_Token_Text(
  6561. $generator->generateFromToken($token, $config, $context)
  6562. );
  6563. if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
  6564. } elseif ($e) {
  6565. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
  6566. }
  6567. continue;
  6568. }
  6569. for ($i = count($skipped_tags) - 1; $i >= 0; $i--) {
  6570. if ($i && $e && !isset($skipped_tags[$i]->armor['MakeWellFormed_TagClosedError'])) {
  6571. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$i]);
  6572. }
  6573. $result[] = $new_token = new HTMLPurifier_Token_End($skipped_tags[$i]->name);
  6574. foreach ($this->injectors as $injector) {
  6575. $injector->notifyEnd($new_token);
  6576. }
  6577. }
  6578. }
  6579. $context->destroy('CurrentNesting');
  6580. $context->destroy('InputTokens');
  6581. $context->destroy('InputIndex');
  6582. $context->destroy('CurrentToken');
  6583. if (!empty($this->currentNesting)) {
  6584. for ($i = count($this->currentNesting) - 1; $i >= 0; $i--) {
  6585. if ($e && !isset($this->currentNesting[$i]->armor['MakeWellFormed_TagClosedError'])) {
  6586. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $this->currentNesting[$i]);
  6587. }
  6588. $result[] = $new_token = new HTMLPurifier_Token_End($this->currentNesting[$i]->name);
  6589. foreach ($this->injectors as $injector) {
  6590. $injector->notifyEnd($new_token);
  6591. }
  6592. }
  6593. }
  6594. unset($this->outputTokens, $this->injectors, $this->currentInjector,
  6595. $this->currentNesting, $this->inputTokens, $this->inputIndex);
  6596. return $result;
  6597. }
  6598. function processToken($token, $config, $context) {
  6599. if (is_array($token)) {
  6600. array_splice($this->inputTokens, $this->inputIndex--, 1, $token);
  6601. if ($this->injectors) {
  6602. $offset = count($token);
  6603. for ($i = 0; $i <= $this->currentInjector; $i++) {
  6604. if (!$this->injectors[$i]->skip) $this->injectors[$i]->skip++;
  6605. $this->injectors[$i]->skip += $offset;
  6606. }
  6607. }
  6608. } elseif ($token) {
  6609. $this->outputTokens[] = $token;
  6610. if ($token instanceof HTMLPurifier_Token_Start) {
  6611. $this->currentNesting[] = $token;
  6612. } elseif ($token instanceof HTMLPurifier_Token_End) {
  6613. array_pop($this->currentNesting);
  6614. }
  6615. }
  6616. }
  6617. }
  6618. class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
  6619. {
  6620. public function execute($tokens, $config, $context) {
  6621. $definition = $config->getHTMLDefinition();
  6622. $generator = new HTMLPurifier_Generator();
  6623. $result = array();
  6624. $escape_invalid_tags = $config->get('Core', 'EscapeInvalidTags');
  6625. $remove_invalid_img = $config->get('Core', 'RemoveInvalidImg');
  6626. $remove_script_contents = $config->get('Core', 'RemoveScriptContents');
  6627. $hidden_elements = $config->get('Core', 'HiddenElements');
  6628. if ($remove_script_contents === true) {
  6629. $hidden_elements['script'] = true;
  6630. } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
  6631. unset($hidden_elements['script']);
  6632. }
  6633. $attr_validator = new HTMLPurifier_AttrValidator();
  6634. $remove_until = false;
  6635. $textify_comments = false;
  6636. $token = false;
  6637. $context->register('CurrentToken', $token);
  6638. $e = false;
  6639. if ($config->get('Core', 'CollectErrors')) {
  6640. $e =& $context->get('ErrorCollector');
  6641. }
  6642. foreach($tokens as $token) {
  6643. if ($remove_until) {
  6644. if (empty($token->is_tag) || $token->name !== $remove_until) {
  6645. continue;
  6646. }
  6647. }
  6648. if (!empty( $token->is_tag )) {
  6649. if (
  6650. isset($definition->info_tag_transform[$token->name])
  6651. ) {
  6652. $original_name = $token->name;
  6653. $token = $definition->
  6654. info_tag_transform[$token->name]->
  6655. transform($token, $config, $context);
  6656. if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
  6657. }
  6658. if (isset($definition->info[$token->name])) {
  6659. if (
  6660. ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
  6661. $definition->info[$token->name]->required_attr &&
  6662. ($token->name != 'img' || $remove_invalid_img)
  6663. ) {
  6664. $attr_validator->validateToken($token, $config, $context);
  6665. $ok = true;
  6666. foreach ($definition->info[$token->name]->required_attr as $name) {
  6667. if (!isset($token->attr[$name])) {
  6668. $ok = false;
  6669. break;
  6670. }
  6671. }
  6672. if (!$ok) {
  6673. if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', $name);
  6674. continue;
  6675. }
  6676. $token->armor['ValidateAttributes'] = true;
  6677. }
  6678. if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
  6679. $textify_comments = $token->name;
  6680. } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
  6681. $textify_comments = false;
  6682. }
  6683. } elseif ($escape_invalid_tags) {
  6684. if ($e) $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
  6685. $token = new HTMLPurifier_Token_Text(
  6686. $generator->generateFromToken($token, $config, $context)
  6687. );
  6688. } else {
  6689. if (isset($hidden_elements[$token->name])) {
  6690. if ($token instanceof HTMLPurifier_Token_Start) {
  6691. $remove_until = $token->name;
  6692. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  6693. } else {
  6694. $remove_until = false;
  6695. }
  6696. if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
  6697. } else {
  6698. if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
  6699. }
  6700. continue;
  6701. }
  6702. } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  6703. if ($textify_comments !== false) {
  6704. $data = $token->data;
  6705. $token = new HTMLPurifier_Token_Text($data);
  6706. } else {
  6707. if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
  6708. continue;
  6709. }
  6710. } elseif ($token instanceof HTMLPurifier_Token_Text) {
  6711. } else {
  6712. continue;
  6713. }
  6714. $result[] = $token;
  6715. }
  6716. if ($remove_until && $e) {
  6717. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
  6718. }
  6719. $context->destroy('CurrentToken');
  6720. return $result;
  6721. }
  6722. }
  6723. class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
  6724. {
  6725. public function execute($tokens, $config, $context) {
  6726. $validator = new HTMLPurifier_AttrValidator();
  6727. $token = false;
  6728. $context->register('CurrentToken', $token);
  6729. foreach ($tokens as $key => $token) {
  6730. if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) continue;
  6731. if (!empty($token->armor['ValidateAttributes'])) continue;
  6732. $validator->validateToken($token, $config, $context);
  6733. $tokens[$key] = $token;
  6734. }
  6735. $context->destroy('CurrentToken');
  6736. return $tokens;
  6737. }
  6738. }
  6739. class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
  6740. {
  6741. public $transform_to = 'span';
  6742. protected $_size_lookup = array(
  6743. '0' => 'xx-small',
  6744. '1' => 'xx-small',
  6745. '2' => 'small',
  6746. '3' => 'medium',
  6747. '4' => 'large',
  6748. '5' => 'x-large',
  6749. '6' => 'xx-large',
  6750. '7' => '300%',
  6751. '-1' => 'smaller',
  6752. '-2' => '60%',
  6753. '+1' => 'larger',
  6754. '+2' => '150%',
  6755. '+3' => '200%',
  6756. '+4' => '300%'
  6757. );
  6758. public function transform($tag, $config, $context) {
  6759. if ($tag instanceof HTMLPurifier_Token_End) {
  6760. $new_tag = clone $tag;
  6761. $new_tag->name = $this->transform_to;
  6762. return $new_tag;
  6763. }
  6764. $attr = $tag->attr;
  6765. $prepend_style = '';
  6766. if (isset($attr['color'])) {
  6767. $prepend_style .= 'color:' . $attr['color'] . ';';
  6768. unset($attr['color']);
  6769. }
  6770. if (isset($attr['face'])) {
  6771. $prepend_style .= 'font-family:' . $attr['face'] . ';';
  6772. unset($attr['face']);
  6773. }
  6774. if (isset($attr['size'])) {
  6775. if ($attr['size']{0} == '+' || $attr['size']{0} == '-') {
  6776. $size = (int) $attr['size'];
  6777. if ($size < -2) $attr['size'] = '-2';
  6778. if ($size > 4) $attr['size'] = '+4';
  6779. } else {
  6780. $size = (int) $attr['size'];
  6781. if ($size > 7) $attr['size'] = '7';
  6782. }
  6783. if (isset($this->_size_lookup[$attr['size']])) {
  6784. $prepend_style .= 'font-size:' .
  6785. $this->_size_lookup[$attr['size']] . ';';
  6786. }
  6787. unset($attr['size']);
  6788. }
  6789. if ($prepend_style) {
  6790. $attr['style'] = isset($attr['style']) ?
  6791. $prepend_style . $attr['style'] :
  6792. $prepend_style;
  6793. }
  6794. $new_tag = clone $tag;
  6795. $new_tag->name = $this->transform_to;
  6796. $new_tag->attr = $attr;
  6797. return $new_tag;
  6798. }
  6799. }
  6800. class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
  6801. {
  6802. protected $style;
  6803. public function __construct($transform_to, $style = null) {
  6804. $this->transform_to = $transform_to;
  6805. $this->style = $style;
  6806. }
  6807. public function transform($tag, $config, $context) {
  6808. $new_tag = clone $tag;
  6809. $new_tag->name = $this->transform_to;
  6810. if (!is_null($this->style) &&
  6811. ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
  6812. ) {
  6813. $this->prependCSS($new_tag->attr, $this->style);
  6814. }
  6815. return $new_tag;
  6816. }
  6817. }
  6818. class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
  6819. {
  6820. public $data; /**< Character data within comment. */
  6821. public function __construct($data, $line = null) {
  6822. $this->data = $data;
  6823. $this->line = $line;
  6824. }
  6825. }
  6826. class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
  6827. {
  6828. public $is_tag = true;
  6829. public $name;
  6830. public $attr = array();
  6831. public function __construct($name, $attr = array(), $line = null) {
  6832. $this->name = ctype_lower($name) ? $name : strtolower($name);
  6833. foreach ($attr as $key => $value) {
  6834. if (!ctype_lower($key)) {
  6835. $new_key = strtolower($key);
  6836. if (!isset($attr[$new_key])) {
  6837. $attr[$new_key] = $attr[$key];
  6838. }
  6839. if ($new_key !== $key) {
  6840. unset($attr[$key]);
  6841. }
  6842. }
  6843. }
  6844. $this->attr = $attr;
  6845. $this->line = $line;
  6846. }
  6847. }
  6848. class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
  6849. {
  6850. }
  6851. class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
  6852. {
  6853. }
  6854. class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
  6855. {
  6856. }
  6857. class HTMLPurifier_Token_Text extends HTMLPurifier_Token
  6858. {
  6859. public $name = '#PCDATA'; /**< PCDATA tag name compatible with DTD. */
  6860. public $data; /**< Parsed character data of text. */
  6861. public $is_whitespace; /**< Bool indicating if node is whitespace. */
  6862. public function __construct($data, $line = null) {
  6863. $this->data = $data;
  6864. $this->is_whitespace = ctype_space($data);
  6865. $this->line = $line;
  6866. }
  6867. }
  6868. class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
  6869. {
  6870. public $name = 'DisableExternal';
  6871. protected $ourHostParts = false;
  6872. public function prepare($config) {
  6873. $our_host = $config->get('URI', 'Host');
  6874. if ($our_host !== null) $this->ourHostParts = array_reverse(explode('.', $our_host));
  6875. }
  6876. public function filter(&$uri, $config, $context) {
  6877. if (is_null($uri->host)) return true;
  6878. if ($this->ourHostParts === false) return false;
  6879. $host_parts = array_reverse(explode('.', $uri->host));
  6880. foreach ($this->ourHostParts as $i => $x) {
  6881. if (!isset($host_parts[$i])) return false;
  6882. if ($host_parts[$i] != $this->ourHostParts[$i]) return false;
  6883. }
  6884. return true;
  6885. }
  6886. }
  6887. class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
  6888. {
  6889. public $name = 'DisableExternalResources';
  6890. public function filter(&$uri, $config, $context) {
  6891. if (!$context->get('EmbeddedURI', true)) return true;
  6892. return parent::filter($uri, $config, $context);
  6893. }
  6894. }
  6895. class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
  6896. {
  6897. public $name = 'HostBlacklist';
  6898. protected $blacklist = array();
  6899. public function prepare($config) {
  6900. $this->blacklist = $config->get('URI', 'HostBlacklist');
  6901. }
  6902. public function filter(&$uri, $config, $context) {
  6903. foreach($this->blacklist as $blacklisted_host_fragment) {
  6904. if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
  6905. return false;
  6906. }
  6907. }
  6908. return true;
  6909. }
  6910. }
  6911. class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
  6912. {
  6913. public $name = 'MakeAbsolute';
  6914. protected $base;
  6915. protected $basePathStack = array();
  6916. public function prepare($config) {
  6917. $def = $config->getDefinition('URI');
  6918. $this->base = $def->base;
  6919. if (is_null($this->base)) {
  6920. trigger_error('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration', E_USER_ERROR);
  6921. return;
  6922. }
  6923. $this->base->fragment = null;
  6924. $stack = explode('/', $this->base->path);
  6925. array_pop($stack);
  6926. $stack = $this->_collapseStack($stack);
  6927. $this->basePathStack = $stack;
  6928. }
  6929. public function filter(&$uri, $config, $context) {
  6930. if (is_null($this->base)) return true;
  6931. if (
  6932. $uri->path === '' && is_null($uri->scheme) &&
  6933. is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)
  6934. ) {
  6935. $uri = clone $this->base;
  6936. return true;
  6937. }
  6938. if (!is_null($uri->scheme)) {
  6939. if (!is_null($uri->host)) return true;
  6940. $scheme_obj = $uri->getSchemeObj($config, $context);
  6941. if (!$scheme_obj) {
  6942. return false;
  6943. }
  6944. if (!$scheme_obj->hierarchical) {
  6945. return true;
  6946. }
  6947. }
  6948. if (!is_null($uri->host)) {
  6949. return true;
  6950. }
  6951. if ($uri->path === '') {
  6952. $uri->path = $this->base->path;
  6953. }elseif ($uri->path[0] !== '/') {
  6954. $stack = explode('/', $uri->path);
  6955. $new_stack = array_merge($this->basePathStack, $stack);
  6956. $new_stack = $this->_collapseStack($new_stack);
  6957. $uri->path = implode('/', $new_stack);
  6958. }
  6959. $uri->scheme = $this->base->scheme;
  6960. if (is_null($uri->userinfo)) $uri->userinfo = $this->base->userinfo;
  6961. if (is_null($uri->host)) $uri->host = $this->base->host;
  6962. if (is_null($uri->port)) $uri->port = $this->base->port;
  6963. return true;
  6964. }
  6965. private function _collapseStack($stack) {
  6966. $result = array();
  6967. for ($i = 0; isset($stack[$i]); $i++) {
  6968. $is_folder = false;
  6969. if ($stack[$i] == '' && $i && isset($stack[$i+1])) continue;
  6970. if ($stack[$i] == '..') {
  6971. if (!empty($result)) {
  6972. $segment = array_pop($result);
  6973. if ($segment === '' && empty($result)) {
  6974. $result[] = '';
  6975. } elseif ($segment === '..') {
  6976. $result[] = '..';
  6977. }
  6978. } else {
  6979. $result[] = '..';
  6980. }
  6981. $is_folder = true;
  6982. continue;
  6983. }
  6984. if ($stack[$i] == '.') {
  6985. $is_folder = true;
  6986. continue;
  6987. }
  6988. $result[] = $stack[$i];
  6989. }
  6990. if ($is_folder) $result[] = '';
  6991. return $result;
  6992. }
  6993. }
  6994. class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme {
  6995. public $default_port = 21;
  6996. public $browsable = true;
  6997. public $hierarchical = true;
  6998. public function validate(&$uri, $config, $context) {
  6999. parent::validate($uri, $config, $context);
  7000. $uri->query = null;
  7001. $semicolon_pos = strrpos($uri->path, ';');
  7002. if ($semicolon_pos !== false) {
  7003. $type = substr($uri->path, $semicolon_pos + 1);
  7004. $uri->path = substr($uri->path, 0, $semicolon_pos);
  7005. $type_ret = '';
  7006. if (strpos($type, '=') !== false) {
  7007. list($key, $typecode) = explode('=', $type, 2);
  7008. if ($key !== 'type') {
  7009. $uri->path .= '%3B' . $type;
  7010. } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
  7011. $type_ret = ";type=$typecode";
  7012. }
  7013. } else {
  7014. $uri->path .= '%3B' . $type;
  7015. }
  7016. $uri->path = str_replace(';', '%3B', $uri->path);
  7017. $uri->path .= $type_ret;
  7018. }
  7019. return true;
  7020. }
  7021. }
  7022. class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme {
  7023. public $default_port = 80;
  7024. public $browsable = true;
  7025. public $hierarchical = true;
  7026. public function validate(&$uri, $config, $context) {
  7027. parent::validate($uri, $config, $context);
  7028. $uri->userinfo = null;
  7029. return true;
  7030. }
  7031. }
  7032. class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http {
  7033. public $default_port = 443;
  7034. }
  7035. class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme {
  7036. public $browsable = false;
  7037. public function validate(&$uri, $config, $context) {
  7038. parent::validate($uri, $config, $context);
  7039. $uri->userinfo = null;
  7040. $uri->host = null;
  7041. $uri->port = null;
  7042. return true;
  7043. }
  7044. }
  7045. class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme {
  7046. public $browsable = false;
  7047. public function validate(&$uri, $config, $context) {
  7048. parent::validate($uri, $config, $context);
  7049. $uri->userinfo = null;
  7050. $uri->host = null;
  7051. $uri->port = null;
  7052. $uri->query = null;
  7053. return true;
  7054. }
  7055. }
  7056. class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme {
  7057. public $default_port = 119;
  7058. public $browsable = false;
  7059. public function validate(&$uri, $config, $context) {
  7060. parent::validate($uri, $config, $context);
  7061. $uri->userinfo = null;
  7062. $uri->query = null;
  7063. return true;
  7064. }
  7065. }
  7066. class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
  7067. {
  7068. protected function parseImplementation($var, $type, $allow_null) {
  7069. if ($allow_null && $var === null) return null;
  7070. switch ($type) {
  7071. case 'mixed':
  7072. case 'istring':
  7073. case 'string':
  7074. case 'text':
  7075. case 'itext':
  7076. return $var;
  7077. case 'int':
  7078. if (is_string($var) && ctype_digit($var)) $var = (int) $var;
  7079. return $var;
  7080. case 'float':
  7081. if ((is_string($var) && is_numeric($var)) || is_int($var)) $var = (float) $var;
  7082. return $var;
  7083. case 'bool':
  7084. if (is_int($var) && ($var === 0 || $var === 1)) {
  7085. $var = (bool) $var;
  7086. } elseif (is_string($var)) {
  7087. if ($var == 'on' || $var == 'true' || $var == '1') {
  7088. $var = true;
  7089. } elseif ($var == 'off' || $var == 'false' || $var == '0') {
  7090. $var = false;
  7091. } else {
  7092. throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");
  7093. }
  7094. }
  7095. return $var;
  7096. case 'list':
  7097. case 'hash':
  7098. case 'lookup':
  7099. if (is_string($var)) {
  7100. if ($var == '') return array();
  7101. if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
  7102. $var = explode(',',$var);
  7103. } else {
  7104. $var = preg_split('/(,|[\n\r]+)/', $var);
  7105. }
  7106. foreach ($var as $i => $j) $var[$i] = trim($j);
  7107. if ($type === 'hash') {
  7108. $nvar = array();
  7109. foreach ($var as $keypair) {
  7110. $c = explode(':', $keypair, 2);
  7111. if (!isset($c[1])) continue;
  7112. $nvar[$c[0]] = $c[1];
  7113. }
  7114. $var = $nvar;
  7115. }
  7116. }
  7117. if (!is_array($var)) break;
  7118. $keys = array_keys($var);
  7119. if ($keys === array_keys($keys)) {
  7120. if ($type == 'list') return $var;
  7121. elseif ($type == 'lookup') {
  7122. $new = array();
  7123. foreach ($var as $key) {
  7124. $new[$key] = true;
  7125. }
  7126. return $new;
  7127. } else break;
  7128. }
  7129. if ($type === 'lookup') {
  7130. foreach ($var as $key => $value) {
  7131. $var[$key] = true;
  7132. }
  7133. }
  7134. return $var;
  7135. default:
  7136. $this->errorInconsistent(__CLASS__, $type);
  7137. }
  7138. $this->errorGeneric($var, $type);
  7139. }
  7140. }
  7141. class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
  7142. {
  7143. protected function parseImplementation($var, $type, $allow_null) {
  7144. return $this->evalExpression($var);
  7145. }
  7146. protected function evalExpression($expr) {
  7147. $var = null;
  7148. $result = eval("\$var = $expr;");
  7149. if ($result === false) {
  7150. throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
  7151. }
  7152. return $var;
  7153. }
  7154. }