PageRenderTime 59ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/moodle/lib/htmlpurifier/HTMLPurifier/ConfigSchema.php

https://bitbucket.org/geek745/moodle-db2
PHP | 436 lines | 315 code | 21 blank | 100 comment | 81 complexity | 9f659380fc70e295eea07607ccde4fd0 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause, LGPL-2.0
  1. <?php
  2. require_once 'HTMLPurifier/Error.php';
  3. require_once 'HTMLPurifier/ConfigDef.php';
  4. require_once 'HTMLPurifier/ConfigDef/Namespace.php';
  5. require_once 'HTMLPurifier/ConfigDef/Directive.php';
  6. require_once 'HTMLPurifier/ConfigDef/DirectiveAlias.php';
  7. if (!defined('HTMLPURIFIER_SCHEMA_STRICT')) define('HTMLPURIFIER_SCHEMA_STRICT', false);
  8. /**
  9. * Configuration definition, defines directives and their defaults.
  10. * @note If you update this, please update Printer_ConfigForm
  11. * @todo The ability to define things multiple times is confusing and should
  12. * be factored out to its own function named registerDependency() or
  13. * addNote(), where only the namespace.name and an extra descriptions
  14. * documenting the nature of the dependency are needed. Since it's
  15. * possible that the dependency is registered before the configuration
  16. * is defined, deferring it to some sort of cache until it actually
  17. * gets defined would be wise, keeping it opaque until it does get
  18. * defined. We could add a finalize() method which would cause it to
  19. * error out if we get a dangling dependency. It's difficult, however,
  20. * to know whether or not it's a dependency, or a codependency, that is
  21. * neither of them fully depends on it. Where does the configuration go
  22. * then? This could be partially resolved by allowing blanket definitions
  23. * and then splitting them up into finer-grained versions, however, there
  24. * might be implementation difficulties in ini files regarding order of
  25. * execution.
  26. */
  27. class HTMLPurifier_ConfigSchema {
  28. /**
  29. * Defaults of the directives and namespaces.
  30. * @note This shares the exact same structure as HTMLPurifier_Config::$conf
  31. */
  32. var $defaults = array();
  33. /**
  34. * Definition of the directives.
  35. */
  36. var $info = array();
  37. /**
  38. * Definition of namespaces.
  39. */
  40. var $info_namespace = array();
  41. /**
  42. * Lookup table of allowed types.
  43. */
  44. var $types = array(
  45. 'string' => 'String',
  46. 'istring' => 'Case-insensitive string',
  47. 'text' => 'Text',
  48. 'itext' => 'Case-insensitive text',
  49. 'int' => 'Integer',
  50. 'float' => 'Float',
  51. 'bool' => 'Boolean',
  52. 'lookup' => 'Lookup array',
  53. 'list' => 'Array list',
  54. 'hash' => 'Associative array',
  55. 'mixed' => 'Mixed'
  56. );
  57. /**
  58. * Initializes the default namespaces.
  59. */
  60. function initialize() {
  61. $this->defineNamespace('Core', 'Core features that are always available.');
  62. $this->defineNamespace('Attr', 'Features regarding attribute validation.');
  63. $this->defineNamespace('URI', 'Features regarding Uniform Resource Identifiers.');
  64. $this->defineNamespace('HTML', 'Configuration regarding allowed HTML.');
  65. $this->defineNamespace('CSS', 'Configuration regarding allowed CSS.');
  66. $this->defineNamespace('AutoFormat', 'Configuration for activating auto-formatting functionality (also known as <code>Injector</code>s)');
  67. $this->defineNamespace('AutoFormatParam', 'Configuration for customizing auto-formatting functionality');
  68. $this->defineNamespace('Output', 'Configuration relating to the generation of (X)HTML.');
  69. $this->defineNamespace('Cache', 'Configuration for DefinitionCache and related subclasses.');
  70. $this->defineNamespace('Test', 'Developer testing configuration for our unit tests.');
  71. }
  72. /**
  73. * Retrieves an instance of the application-wide configuration definition.
  74. * @static
  75. */
  76. function &instance($prototype = null) {
  77. static $instance;
  78. if ($prototype !== null) {
  79. $instance = $prototype;
  80. } elseif ($instance === null || $prototype === true) {
  81. $instance = new HTMLPurifier_ConfigSchema();
  82. $instance->initialize();
  83. }
  84. return $instance;
  85. }
  86. /**
  87. * Defines a directive for configuration
  88. * @static
  89. * @warning Will fail of directive's namespace is defined
  90. * @param $namespace Namespace the directive is in
  91. * @param $name Key of directive
  92. * @param $default Default value of directive
  93. * @param $type Allowed type of the directive. See
  94. * HTMLPurifier_DirectiveDef::$type for allowed values
  95. * @param $description Description of directive for documentation
  96. */
  97. function define($namespace, $name, $default, $type, $description) {
  98. $def =& HTMLPurifier_ConfigSchema::instance();
  99. // basic sanity checks
  100. if (HTMLPURIFIER_SCHEMA_STRICT) {
  101. if (!isset($def->info[$namespace])) {
  102. trigger_error('Cannot define directive for undefined namespace',
  103. E_USER_ERROR);
  104. return;
  105. }
  106. if (!ctype_alnum($name)) {
  107. trigger_error('Directive name must be alphanumeric',
  108. E_USER_ERROR);
  109. return;
  110. }
  111. if (empty($description)) {
  112. trigger_error('Description must be non-empty',
  113. E_USER_ERROR);
  114. return;
  115. }
  116. }
  117. if (isset($def->info[$namespace][$name])) {
  118. // already defined
  119. if (
  120. $def->info[$namespace][$name]->type !== $type ||
  121. $def->defaults[$namespace][$name] !== $default
  122. ) {
  123. trigger_error('Inconsistent default or type, cannot redefine');
  124. return;
  125. }
  126. } else {
  127. // needs defining
  128. // process modifiers (OPTIMIZE!)
  129. $type_values = explode('/', $type, 2);
  130. $type = $type_values[0];
  131. $modifier = isset($type_values[1]) ? $type_values[1] : false;
  132. $allow_null = ($modifier === 'null');
  133. if (HTMLPURIFIER_SCHEMA_STRICT) {
  134. if (!isset($def->types[$type])) {
  135. trigger_error('Invalid type for configuration directive',
  136. E_USER_ERROR);
  137. return;
  138. }
  139. $default = $def->validate($default, $type, $allow_null);
  140. if ($def->isError($default)) {
  141. trigger_error('Default value does not match directive type',
  142. E_USER_ERROR);
  143. return;
  144. }
  145. }
  146. $def->info[$namespace][$name] =
  147. new HTMLPurifier_ConfigDef_Directive();
  148. $def->info[$namespace][$name]->type = $type;
  149. $def->info[$namespace][$name]->allow_null = $allow_null;
  150. $def->defaults[$namespace][$name] = $default;
  151. }
  152. if (!HTMLPURIFIER_SCHEMA_STRICT) return;
  153. $backtrace = debug_backtrace();
  154. $file = $def->mungeFilename($backtrace[0]['file']);
  155. $line = $backtrace[0]['line'];
  156. $def->info[$namespace][$name]->addDescription($file,$line,$description);
  157. }
  158. /**
  159. * Defines a namespace for directives to be put into.
  160. * @static
  161. * @param $namespace Namespace's name
  162. * @param $description Description of the namespace
  163. */
  164. function defineNamespace($namespace, $description) {
  165. $def =& HTMLPurifier_ConfigSchema::instance();
  166. if (HTMLPURIFIER_SCHEMA_STRICT) {
  167. if (isset($def->info[$namespace])) {
  168. trigger_error('Cannot redefine namespace', E_USER_ERROR);
  169. return;
  170. }
  171. if (!ctype_alnum($namespace)) {
  172. trigger_error('Namespace name must be alphanumeric',
  173. E_USER_ERROR);
  174. return;
  175. }
  176. if (empty($description)) {
  177. trigger_error('Description must be non-empty',
  178. E_USER_ERROR);
  179. return;
  180. }
  181. }
  182. $def->info[$namespace] = array();
  183. $def->info_namespace[$namespace] = new HTMLPurifier_ConfigDef_Namespace();
  184. $def->info_namespace[$namespace]->description = $description;
  185. $def->defaults[$namespace] = array();
  186. }
  187. /**
  188. * Defines a directive value alias.
  189. *
  190. * Directive value aliases are convenient for developers because it lets
  191. * them set a directive to several values and get the same result.
  192. * @static
  193. * @param $namespace Directive's namespace
  194. * @param $name Name of Directive
  195. * @param $alias Name of aliased value
  196. * @param $real Value aliased value will be converted into
  197. */
  198. function defineValueAliases($namespace, $name, $aliases) {
  199. $def =& HTMLPurifier_ConfigSchema::instance();
  200. if (HTMLPURIFIER_SCHEMA_STRICT && !isset($def->info[$namespace][$name])) {
  201. trigger_error('Cannot set value alias for non-existant directive',
  202. E_USER_ERROR);
  203. return;
  204. }
  205. foreach ($aliases as $alias => $real) {
  206. if (HTMLPURIFIER_SCHEMA_STRICT) {
  207. if (!$def->info[$namespace][$name] !== true &&
  208. !isset($def->info[$namespace][$name]->allowed[$real])
  209. ) {
  210. trigger_error('Cannot define alias to value that is not allowed',
  211. E_USER_ERROR);
  212. return;
  213. }
  214. if (isset($def->info[$namespace][$name]->allowed[$alias])) {
  215. trigger_error('Cannot define alias over allowed value',
  216. E_USER_ERROR);
  217. return;
  218. }
  219. }
  220. $def->info[$namespace][$name]->aliases[$alias] = $real;
  221. }
  222. }
  223. /**
  224. * Defines a set of allowed values for a directive.
  225. * @static
  226. * @param $namespace Namespace of directive
  227. * @param $name Name of directive
  228. * @param $allowed_values Arraylist of allowed values
  229. */
  230. function defineAllowedValues($namespace, $name, $allowed_values) {
  231. $def =& HTMLPurifier_ConfigSchema::instance();
  232. if (HTMLPURIFIER_SCHEMA_STRICT && !isset($def->info[$namespace][$name])) {
  233. trigger_error('Cannot define allowed values for undefined directive',
  234. E_USER_ERROR);
  235. return;
  236. }
  237. $directive =& $def->info[$namespace][$name];
  238. $type = $directive->type;
  239. if (HTMLPURIFIER_SCHEMA_STRICT && $type != 'string' && $type != 'istring') {
  240. trigger_error('Cannot define allowed values for directive whose type is not string',
  241. E_USER_ERROR);
  242. return;
  243. }
  244. if ($directive->allowed === true) {
  245. $directive->allowed = array();
  246. }
  247. foreach ($allowed_values as $value) {
  248. $directive->allowed[$value] = true;
  249. }
  250. if (
  251. HTMLPURIFIER_SCHEMA_STRICT &&
  252. $def->defaults[$namespace][$name] !== null &&
  253. !isset($directive->allowed[$def->defaults[$namespace][$name]])
  254. ) {
  255. trigger_error('Default value must be in allowed range of variables',
  256. E_USER_ERROR);
  257. $directive->allowed = true; // undo undo!
  258. return;
  259. }
  260. }
  261. /**
  262. * Defines a directive alias for backwards compatibility
  263. * @static
  264. * @param $namespace
  265. * @param $name Directive that will be aliased
  266. * @param $new_namespace
  267. * @param $new_name Directive that the alias will be to
  268. */
  269. function defineAlias($namespace, $name, $new_namespace, $new_name) {
  270. $def =& HTMLPurifier_ConfigSchema::instance();
  271. if (HTMLPURIFIER_SCHEMA_STRICT) {
  272. if (!isset($def->info[$namespace])) {
  273. trigger_error('Cannot define directive alias in undefined namespace',
  274. E_USER_ERROR);
  275. return;
  276. }
  277. if (!ctype_alnum($name)) {
  278. trigger_error('Directive name must be alphanumeric',
  279. E_USER_ERROR);
  280. return;
  281. }
  282. if (isset($def->info[$namespace][$name])) {
  283. trigger_error('Cannot define alias over directive',
  284. E_USER_ERROR);
  285. return;
  286. }
  287. if (!isset($def->info[$new_namespace][$new_name])) {
  288. trigger_error('Cannot define alias to undefined directive',
  289. E_USER_ERROR);
  290. return;
  291. }
  292. if ($def->info[$new_namespace][$new_name]->class == 'alias') {
  293. trigger_error('Cannot define alias to alias',
  294. E_USER_ERROR);
  295. return;
  296. }
  297. }
  298. $def->info[$namespace][$name] =
  299. new HTMLPurifier_ConfigDef_DirectiveAlias(
  300. $new_namespace, $new_name);
  301. $def->info[$new_namespace][$new_name]->directiveAliases[] = "$namespace.$name";
  302. }
  303. /**
  304. * Validate a variable according to type. Return null if invalid.
  305. */
  306. function validate($var, $type, $allow_null = false) {
  307. if (!isset($this->types[$type])) {
  308. trigger_error('Invalid type', E_USER_ERROR);
  309. return;
  310. }
  311. if ($allow_null && $var === null) return null;
  312. switch ($type) {
  313. case 'mixed':
  314. //if (is_string($var)) $var = unserialize($var);
  315. return $var;
  316. case 'istring':
  317. case 'string':
  318. case 'text': // no difference, just is longer/multiple line string
  319. case 'itext':
  320. if (!is_string($var)) break;
  321. if ($type === 'istring' || $type === 'itext') $var = strtolower($var);
  322. return $var;
  323. case 'int':
  324. if (is_string($var) && ctype_digit($var)) $var = (int) $var;
  325. elseif (!is_int($var)) break;
  326. return $var;
  327. case 'float':
  328. if (is_string($var) && is_numeric($var)) $var = (float) $var;
  329. elseif (!is_float($var)) break;
  330. return $var;
  331. case 'bool':
  332. if (is_int($var) && ($var === 0 || $var === 1)) {
  333. $var = (bool) $var;
  334. } elseif (is_string($var)) {
  335. if ($var == 'on' || $var == 'true' || $var == '1') {
  336. $var = true;
  337. } elseif ($var == 'off' || $var == 'false' || $var == '0') {
  338. $var = false;
  339. } else {
  340. break;
  341. }
  342. } elseif (!is_bool($var)) break;
  343. return $var;
  344. case 'list':
  345. case 'hash':
  346. case 'lookup':
  347. if (is_string($var)) {
  348. // special case: technically, this is an array with
  349. // a single empty string item, but having an empty
  350. // array is more intuitive
  351. if ($var == '') return array();
  352. if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
  353. // simplistic string to array method that only works
  354. // for simple lists of tag names or alphanumeric characters
  355. $var = explode(',',$var);
  356. } else {
  357. $var = preg_split('/(,|[\n\r]+)/', $var);
  358. }
  359. // remove spaces
  360. foreach ($var as $i => $j) $var[$i] = trim($j);
  361. if ($type === 'hash') {
  362. // key:value,key2:value2
  363. $nvar = array();
  364. foreach ($var as $keypair) {
  365. $c = explode(':', $keypair, 2);
  366. if (!isset($c[1])) continue;
  367. $nvar[$c[0]] = $c[1];
  368. }
  369. $var = $nvar;
  370. }
  371. }
  372. if (!is_array($var)) break;
  373. $keys = array_keys($var);
  374. if ($keys === array_keys($keys)) {
  375. if ($type == 'list') return $var;
  376. elseif ($type == 'lookup') {
  377. $new = array();
  378. foreach ($var as $key) {
  379. $new[$key] = true;
  380. }
  381. return $new;
  382. } else break;
  383. }
  384. if ($type === 'lookup') {
  385. foreach ($var as $key => $value) {
  386. $var[$key] = true;
  387. }
  388. }
  389. return $var;
  390. }
  391. $error = new HTMLPurifier_Error();
  392. return $error;
  393. }
  394. /**
  395. * Takes an absolute path and munges it into a more manageable relative path
  396. */
  397. function mungeFilename($filename) {
  398. if (!HTMLPURIFIER_SCHEMA_STRICT) return $filename;
  399. $offset = strrpos($filename, 'HTMLPurifier');
  400. $filename = substr($filename, $offset);
  401. $filename = str_replace('\\', '/', $filename);
  402. return $filename;
  403. }
  404. /**
  405. * Checks if var is an HTMLPurifier_Error object
  406. */
  407. function isError($var) {
  408. if (!is_object($var)) return false;
  409. if (!is_a($var, 'HTMLPurifier_Error')) return false;
  410. return true;
  411. }
  412. }