PageRenderTime 50ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/external_lib/HTMLPurifier/HTMLPurifier/Config.php

https://github.com/OwlManAtt/kittokittokitto
PHP | 580 lines | 332 code | 47 blank | 201 comment | 80 complexity | aefeb82b17623a6d0594a084afb664b4 MD5 | raw file
  1. <?php
  2. /**
  3. * Configuration object that triggers customizable behavior.
  4. *
  5. * @warning This class is strongly defined: that means that the class
  6. * will fail if an undefined directive is retrieved or set.
  7. *
  8. * @note Many classes that could (although many times don't) use the
  9. * configuration object make it a mandatory parameter. This is
  10. * because a configuration object should always be forwarded,
  11. * otherwise, you run the risk of missing a parameter and then
  12. * being stumped when a configuration directive doesn't work.
  13. *
  14. * @todo Reconsider some of the public member variables
  15. */
  16. class HTMLPurifier_Config
  17. {
  18. /**
  19. * HTML Purifier's version
  20. */
  21. public $version = '4.0.0';
  22. /**
  23. * Bool indicator whether or not to automatically finalize
  24. * the object if a read operation is done
  25. */
  26. public $autoFinalize = true;
  27. // protected member variables
  28. /**
  29. * Namespace indexed array of serials for specific namespaces (see
  30. * getSerial() for more info).
  31. */
  32. protected $serials = array();
  33. /**
  34. * Serial for entire configuration object
  35. */
  36. protected $serial;
  37. /**
  38. * Parser for variables
  39. */
  40. protected $parser;
  41. /**
  42. * Reference HTMLPurifier_ConfigSchema for value checking
  43. * @note This is public for introspective purposes. Please don't
  44. * abuse!
  45. */
  46. public $def;
  47. /**
  48. * Indexed array of definitions
  49. */
  50. protected $definitions;
  51. /**
  52. * Bool indicator whether or not config is finalized
  53. */
  54. protected $finalized = false;
  55. /**
  56. * Property list containing configuration directives.
  57. */
  58. protected $plist;
  59. /**
  60. * Whether or not a set is taking place due to an
  61. * alias lookup.
  62. */
  63. private $aliasMode;
  64. /**
  65. * Set to false if you do not want line and file numbers in errors
  66. * (useful when unit testing)
  67. */
  68. public $chatty = true;
  69. /**
  70. * Current lock; only gets to this namespace are allowed.
  71. */
  72. private $lock;
  73. /**
  74. * @param $definition HTMLPurifier_ConfigSchema that defines what directives
  75. * are allowed.
  76. */
  77. public function __construct($definition, $parent = null) {
  78. $parent = $parent ? $parent : $definition->defaultPlist;
  79. $this->plist = new HTMLPurifier_PropertyList($parent);
  80. $this->def = $definition; // keep a copy around for checking
  81. $this->parser = new HTMLPurifier_VarParser_Flexible();
  82. }
  83. /**
  84. * Convenience constructor that creates a config object based on a mixed var
  85. * @param mixed $config Variable that defines the state of the config
  86. * object. Can be: a HTMLPurifier_Config() object,
  87. * an array of directives based on loadArray(),
  88. * or a string filename of an ini file.
  89. * @param HTMLPurifier_ConfigSchema Schema object
  90. * @return Configured HTMLPurifier_Config object
  91. */
  92. public static function create($config, $schema = null) {
  93. if ($config instanceof HTMLPurifier_Config) {
  94. // pass-through
  95. return $config;
  96. }
  97. if (!$schema) {
  98. $ret = HTMLPurifier_Config::createDefault();
  99. } else {
  100. $ret = new HTMLPurifier_Config($schema);
  101. }
  102. if (is_string($config)) $ret->loadIni($config);
  103. elseif (is_array($config)) $ret->loadArray($config);
  104. return $ret;
  105. }
  106. /**
  107. * Creates a new config object that inherits from a previous one.
  108. * @param HTMLPurifier_Config $config Configuration object to inherit
  109. * from.
  110. * @return HTMLPurifier_Config object with $config as its parent.
  111. */
  112. public static function inherit(HTMLPurifier_Config $config) {
  113. return new HTMLPurifier_Config($config->def, $config->plist);
  114. }
  115. /**
  116. * Convenience constructor that creates a default configuration object.
  117. * @return Default HTMLPurifier_Config object.
  118. */
  119. public static function createDefault() {
  120. $definition = HTMLPurifier_ConfigSchema::instance();
  121. $config = new HTMLPurifier_Config($definition);
  122. return $config;
  123. }
  124. /**
  125. * Retreives a value from the configuration.
  126. * @param $key String key
  127. */
  128. public function get($key, $a = null) {
  129. if ($a !== null) {
  130. $this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING);
  131. $key = "$key.$a";
  132. }
  133. if (!$this->finalized) $this->autoFinalize();
  134. if (!isset($this->def->info[$key])) {
  135. // can't add % due to SimpleTest bug
  136. $this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
  137. E_USER_WARNING);
  138. return;
  139. }
  140. if (isset($this->def->info[$key]->isAlias)) {
  141. $d = $this->def->info[$key];
  142. $this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key,
  143. E_USER_ERROR);
  144. return;
  145. }
  146. if ($this->lock) {
  147. list($ns) = explode('.', $key);
  148. if ($ns !== $this->lock) {
  149. $this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR);
  150. return;
  151. }
  152. }
  153. return $this->plist->get($key);
  154. }
  155. /**
  156. * Retreives an array of directives to values from a given namespace
  157. * @param $namespace String namespace
  158. */
  159. public function getBatch($namespace) {
  160. if (!$this->finalized) $this->autoFinalize();
  161. $full = $this->getAll();
  162. if (!isset($full[$namespace])) {
  163. $this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace),
  164. E_USER_WARNING);
  165. return;
  166. }
  167. return $full[$namespace];
  168. }
  169. /**
  170. * Returns a md5 signature of a segment of the configuration object
  171. * that uniquely identifies that particular configuration
  172. * @note Revision is handled specially and is removed from the batch
  173. * before processing!
  174. * @param $namespace Namespace to get serial for
  175. */
  176. public function getBatchSerial($namespace) {
  177. if (empty($this->serials[$namespace])) {
  178. $batch = $this->getBatch($namespace);
  179. unset($batch['DefinitionRev']);
  180. $this->serials[$namespace] = md5(serialize($batch));
  181. }
  182. return $this->serials[$namespace];
  183. }
  184. /**
  185. * Returns a md5 signature for the entire configuration object
  186. * that uniquely identifies that particular configuration
  187. */
  188. public function getSerial() {
  189. if (empty($this->serial)) {
  190. $this->serial = md5(serialize($this->getAll()));
  191. }
  192. return $this->serial;
  193. }
  194. /**
  195. * Retrieves all directives, organized by namespace
  196. * @warning This is a pretty inefficient function, avoid if you can
  197. */
  198. public function getAll() {
  199. if (!$this->finalized) $this->autoFinalize();
  200. $ret = array();
  201. foreach ($this->plist->squash() as $name => $value) {
  202. list($ns, $key) = explode('.', $name, 2);
  203. $ret[$ns][$key] = $value;
  204. }
  205. return $ret;
  206. }
  207. /**
  208. * Sets a value to configuration.
  209. * @param $key String key
  210. * @param $value Mixed value
  211. */
  212. public function set($key, $value, $a = null) {
  213. if (strpos($key, '.') === false) {
  214. $namespace = $key;
  215. $directive = $value;
  216. $value = $a;
  217. $key = "$key.$directive";
  218. $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
  219. } else {
  220. list($namespace) = explode('.', $key);
  221. }
  222. if ($this->isFinalized('Cannot set directive after finalization')) return;
  223. if (!isset($this->def->info[$key])) {
  224. $this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
  225. E_USER_WARNING);
  226. return;
  227. }
  228. $def = $this->def->info[$key];
  229. if (isset($def->isAlias)) {
  230. if ($this->aliasMode) {
  231. $this->triggerError('Double-aliases not allowed, please fix '.
  232. 'ConfigSchema bug with' . $key, E_USER_ERROR);
  233. return;
  234. }
  235. $this->aliasMode = true;
  236. $this->set($def->key, $value);
  237. $this->aliasMode = false;
  238. $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
  239. return;
  240. }
  241. // Raw type might be negative when using the fully optimized form
  242. // of stdclass, which indicates allow_null == true
  243. $rtype = is_int($def) ? $def : $def->type;
  244. if ($rtype < 0) {
  245. $type = -$rtype;
  246. $allow_null = true;
  247. } else {
  248. $type = $rtype;
  249. $allow_null = isset($def->allow_null);
  250. }
  251. try {
  252. $value = $this->parser->parse($value, $type, $allow_null);
  253. } catch (HTMLPurifier_VarParserException $e) {
  254. $this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING);
  255. return;
  256. }
  257. if (is_string($value) && is_object($def)) {
  258. // resolve value alias if defined
  259. if (isset($def->aliases[$value])) {
  260. $value = $def->aliases[$value];
  261. }
  262. // check to see if the value is allowed
  263. if (isset($def->allowed) && !isset($def->allowed[$value])) {
  264. $this->triggerError('Value not supported, valid values are: ' .
  265. $this->_listify($def->allowed), E_USER_WARNING);
  266. return;
  267. }
  268. }
  269. $this->plist->set($key, $value);
  270. // reset definitions if the directives they depend on changed
  271. // this is a very costly process, so it's discouraged
  272. // with finalization
  273. if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
  274. $this->definitions[$namespace] = null;
  275. }
  276. $this->serials[$namespace] = false;
  277. }
  278. /**
  279. * Convenience function for error reporting
  280. */
  281. private function _listify($lookup) {
  282. $list = array();
  283. foreach ($lookup as $name => $b) $list[] = $name;
  284. return implode(', ', $list);
  285. }
  286. /**
  287. * Retrieves object reference to the HTML definition.
  288. * @param $raw Return a copy that has not been setup yet. Must be
  289. * called before it's been setup, otherwise won't work.
  290. */
  291. public function getHTMLDefinition($raw = false) {
  292. return $this->getDefinition('HTML', $raw);
  293. }
  294. /**
  295. * Retrieves object reference to the CSS definition
  296. * @param $raw Return a copy that has not been setup yet. Must be
  297. * called before it's been setup, otherwise won't work.
  298. */
  299. public function getCSSDefinition($raw = false) {
  300. return $this->getDefinition('CSS', $raw);
  301. }
  302. /**
  303. * Retrieves a definition
  304. * @param $type Type of definition: HTML, CSS, etc
  305. * @param $raw Whether or not definition should be returned raw
  306. */
  307. public function getDefinition($type, $raw = false) {
  308. if (!$this->finalized) $this->autoFinalize();
  309. // temporarily suspend locks, so we can handle recursive definition calls
  310. $lock = $this->lock;
  311. $this->lock = null;
  312. $factory = HTMLPurifier_DefinitionCacheFactory::instance();
  313. $cache = $factory->create($type, $this);
  314. $this->lock = $lock;
  315. if (!$raw) {
  316. // see if we can quickly supply a definition
  317. if (!empty($this->definitions[$type])) {
  318. if (!$this->definitions[$type]->setup) {
  319. $this->definitions[$type]->setup($this);
  320. $cache->set($this->definitions[$type], $this);
  321. }
  322. return $this->definitions[$type];
  323. }
  324. // memory check missed, try cache
  325. $this->definitions[$type] = $cache->get($this);
  326. if ($this->definitions[$type]) {
  327. // definition in cache, return it
  328. return $this->definitions[$type];
  329. }
  330. } elseif (
  331. !empty($this->definitions[$type]) &&
  332. !$this->definitions[$type]->setup
  333. ) {
  334. // raw requested, raw in memory, quick return
  335. return $this->definitions[$type];
  336. }
  337. // quick checks failed, let's create the object
  338. if ($type == 'HTML') {
  339. $this->definitions[$type] = new HTMLPurifier_HTMLDefinition();
  340. } elseif ($type == 'CSS') {
  341. $this->definitions[$type] = new HTMLPurifier_CSSDefinition();
  342. } elseif ($type == 'URI') {
  343. $this->definitions[$type] = new HTMLPurifier_URIDefinition();
  344. } else {
  345. throw new HTMLPurifier_Exception("Definition of $type type not supported");
  346. }
  347. // quick abort if raw
  348. if ($raw) {
  349. if (is_null($this->get($type . '.DefinitionID'))) {
  350. // fatally error out if definition ID not set
  351. throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID");
  352. }
  353. return $this->definitions[$type];
  354. }
  355. // set it up
  356. $this->lock = $type;
  357. $this->definitions[$type]->setup($this);
  358. $this->lock = null;
  359. // save in cache
  360. $cache->set($this->definitions[$type], $this);
  361. return $this->definitions[$type];
  362. }
  363. /**
  364. * Loads configuration values from an array with the following structure:
  365. * Namespace.Directive => Value
  366. * @param $config_array Configuration associative array
  367. */
  368. public function loadArray($config_array) {
  369. if ($this->isFinalized('Cannot load directives after finalization')) return;
  370. foreach ($config_array as $key => $value) {
  371. $key = str_replace('_', '.', $key);
  372. if (strpos($key, '.') !== false) {
  373. $this->set($key, $value);
  374. } else {
  375. $namespace = $key;
  376. $namespace_values = $value;
  377. foreach ($namespace_values as $directive => $value) {
  378. $this->set($namespace .'.'. $directive, $value);
  379. }
  380. }
  381. }
  382. }
  383. /**
  384. * Returns a list of array(namespace, directive) for all directives
  385. * that are allowed in a web-form context as per an allowed
  386. * namespaces/directives list.
  387. * @param $allowed List of allowed namespaces/directives
  388. */
  389. public static function getAllowedDirectivesForForm($allowed, $schema = null) {
  390. if (!$schema) {
  391. $schema = HTMLPurifier_ConfigSchema::instance();
  392. }
  393. if ($allowed !== true) {
  394. if (is_string($allowed)) $allowed = array($allowed);
  395. $allowed_ns = array();
  396. $allowed_directives = array();
  397. $blacklisted_directives = array();
  398. foreach ($allowed as $ns_or_directive) {
  399. if (strpos($ns_or_directive, '.') !== false) {
  400. // directive
  401. if ($ns_or_directive[0] == '-') {
  402. $blacklisted_directives[substr($ns_or_directive, 1)] = true;
  403. } else {
  404. $allowed_directives[$ns_or_directive] = true;
  405. }
  406. } else {
  407. // namespace
  408. $allowed_ns[$ns_or_directive] = true;
  409. }
  410. }
  411. }
  412. $ret = array();
  413. foreach ($schema->info as $key => $def) {
  414. list($ns, $directive) = explode('.', $key, 2);
  415. if ($allowed !== true) {
  416. if (isset($blacklisted_directives["$ns.$directive"])) continue;
  417. if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
  418. }
  419. if (isset($def->isAlias)) continue;
  420. if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
  421. $ret[] = array($ns, $directive);
  422. }
  423. return $ret;
  424. }
  425. /**
  426. * Loads configuration values from $_GET/$_POST that were posted
  427. * via ConfigForm
  428. * @param $array $_GET or $_POST array to import
  429. * @param $index Index/name that the config variables are in
  430. * @param $allowed List of allowed namespaces/directives
  431. * @param $mq_fix Boolean whether or not to enable magic quotes fix
  432. * @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy
  433. */
  434. public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
  435. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
  436. $config = HTMLPurifier_Config::create($ret, $schema);
  437. return $config;
  438. }
  439. /**
  440. * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
  441. * @note Same parameters as loadArrayFromForm
  442. */
  443. public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) {
  444. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
  445. $this->loadArray($ret);
  446. }
  447. /**
  448. * Prepares an array from a form into something usable for the more
  449. * strict parts of HTMLPurifier_Config
  450. */
  451. public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
  452. if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
  453. $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
  454. $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
  455. $ret = array();
  456. foreach ($allowed as $key) {
  457. list($ns, $directive) = $key;
  458. $skey = "$ns.$directive";
  459. if (!empty($array["Null_$skey"])) {
  460. $ret[$ns][$directive] = null;
  461. continue;
  462. }
  463. if (!isset($array[$skey])) continue;
  464. $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
  465. $ret[$ns][$directive] = $value;
  466. }
  467. return $ret;
  468. }
  469. /**
  470. * Loads configuration values from an ini file
  471. * @param $filename Name of ini file
  472. */
  473. public function loadIni($filename) {
  474. if ($this->isFinalized('Cannot load directives after finalization')) return;
  475. $array = parse_ini_file($filename, true);
  476. $this->loadArray($array);
  477. }
  478. /**
  479. * Checks whether or not the configuration object is finalized.
  480. * @param $error String error message, or false for no error
  481. */
  482. public function isFinalized($error = false) {
  483. if ($this->finalized && $error) {
  484. $this->triggerError($error, E_USER_ERROR);
  485. }
  486. return $this->finalized;
  487. }
  488. /**
  489. * Finalizes configuration only if auto finalize is on and not
  490. * already finalized
  491. */
  492. public function autoFinalize() {
  493. if ($this->autoFinalize) {
  494. $this->finalize();
  495. } else {
  496. $this->plist->squash(true);
  497. }
  498. }
  499. /**
  500. * Finalizes a configuration object, prohibiting further change
  501. */
  502. public function finalize() {
  503. $this->finalized = true;
  504. unset($this->parser);
  505. }
  506. /**
  507. * Produces a nicely formatted error message by supplying the
  508. * stack frame information from two levels up and OUTSIDE of
  509. * HTMLPurifier_Config.
  510. */
  511. protected function triggerError($msg, $no) {
  512. // determine previous stack frame
  513. $backtrace = debug_backtrace();
  514. if ($this->chatty && isset($backtrace[1])) {
  515. $frame = $backtrace[1];
  516. $extra = " on line {$frame['line']} in file {$frame['file']}";
  517. } else {
  518. $extra = '';
  519. }
  520. trigger_error($msg . $extra, $no);
  521. }
  522. /**
  523. * Returns a serialized form of the configuration object that can
  524. * be reconstituted.
  525. */
  526. public function serialize() {
  527. $this->getDefinition('HTML');
  528. $this->getDefinition('CSS');
  529. $this->getDefinition('URI');
  530. return serialize($this);
  531. }
  532. }
  533. // vim: et sw=4 sts=4