Injector.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <?php
  2. /**
  3. * Injects tokens into the document while parsing for well-formedness.
  4. * This enables "formatter-like" functionality such as auto-paragraphing,
  5. * smiley-ification and linkification to take place.
  6. *
  7. * A note on how handlers create changes; this is done by assigning a new
  8. * value to the $token reference. These values can take a variety of forms and
  9. * are best described HTMLPurifier_Strategy_MakeWellFormed->processToken()
  10. * documentation.
  11. *
  12. * @todo Allow injectors to request a re-run on their output. This
  13. * would help if an operation is recursive.
  14. */
  15. abstract class HTMLPurifier_Injector
  16. {
  17. /**
  18. * Advisory name of injector, this is for friendly error messages.
  19. * @type string
  20. */
  21. public $name;
  22. /**
  23. * @type HTMLPurifier_HTMLDefinition
  24. */
  25. protected $htmlDefinition;
  26. /**
  27. * Reference to CurrentNesting variable in Context. This is an array
  28. * list of tokens that we are currently "inside"
  29. * @type array
  30. */
  31. protected $currentNesting;
  32. /**
  33. * Reference to current token.
  34. * @type HTMLPurifier_Token
  35. */
  36. protected $currentToken;
  37. /**
  38. * Reference to InputZipper variable in Context.
  39. * @type HTMLPurifier_Zipper
  40. */
  41. protected $inputZipper;
  42. /**
  43. * Array of elements and attributes this injector creates and therefore
  44. * need to be allowed by the definition. Takes form of
  45. * array('element' => array('attr', 'attr2'), 'element2')
  46. * @type array
  47. */
  48. public $needed = array();
  49. /**
  50. * Number of elements to rewind backwards (relative).
  51. * @type bool|int
  52. */
  53. protected $rewindOffset = false;
  54. /**
  55. * Rewind to a spot to re-perform processing. This is useful if you
  56. * deleted a node, and now need to see if this change affected any
  57. * earlier nodes. Rewinding does not affect other injectors, and can
  58. * result in infinite loops if not used carefully.
  59. * @param bool|int $offset
  60. * @warning HTML Purifier will prevent you from fast-forwarding with this
  61. * function.
  62. */
  63. public function rewindOffset($offset)
  64. {
  65. $this->rewindOffset = $offset;
  66. }
  67. /**
  68. * Retrieves rewind offset, and then unsets it.
  69. * @return bool|int
  70. */
  71. public function getRewindOffset()
  72. {
  73. $r = $this->rewindOffset;
  74. $this->rewindOffset = false;
  75. return $r;
  76. }
  77. /**
  78. * Prepares the injector by giving it the config and context objects:
  79. * this allows references to important variables to be made within
  80. * the injector. This function also checks if the HTML environment
  81. * will work with the Injector (see checkNeeded()).
  82. * @param HTMLPurifier_Config $config
  83. * @param HTMLPurifier_Context $context
  84. * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
  85. */
  86. public function prepare($config, $context)
  87. {
  88. $this->htmlDefinition = $config->getHTMLDefinition();
  89. // Even though this might fail, some unit tests ignore this and
  90. // still test checkNeeded, so be careful. Maybe get rid of that
  91. // dependency.
  92. $result = $this->checkNeeded($config);
  93. if ($result !== false) {
  94. return $result;
  95. }
  96. $this->currentNesting =& $context->get('CurrentNesting');
  97. $this->currentToken =& $context->get('CurrentToken');
  98. $this->inputZipper =& $context->get('InputZipper');
  99. return false;
  100. }
  101. /**
  102. * This function checks if the HTML environment
  103. * will work with the Injector: if p tags are not allowed, the
  104. * Auto-Paragraphing injector should not be enabled.
  105. * @param HTMLPurifier_Config $config
  106. * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
  107. */
  108. public function checkNeeded($config)
  109. {
  110. $def = $config->getHTMLDefinition();
  111. foreach ($this->needed as $element => $attributes) {
  112. if (is_int($element)) {
  113. $element = $attributes;
  114. }
  115. if (!isset($def->info[$element])) {
  116. return $element;
  117. }
  118. if (!is_array($attributes)) {
  119. continue;
  120. }
  121. foreach ($attributes as $name) {
  122. if (!isset($def->info[$element]->attr[$name])) {
  123. return "$element.$name";
  124. }
  125. }
  126. }
  127. return false;
  128. }
  129. /**
  130. * Tests if the context node allows a certain element
  131. * @param string $name Name of element to test for
  132. * @return bool True if element is allowed, false if it is not
  133. */
  134. public function allowsElement($name)
  135. {
  136. if (!empty($this->currentNesting)) {
  137. $parent_token = array_pop($this->currentNesting);
  138. $this->currentNesting[] = $parent_token;
  139. $parent = $this->htmlDefinition->info[$parent_token->name];
  140. } else {
  141. $parent = $this->htmlDefinition->info_parent_def;
  142. }
  143. if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
  144. return false;
  145. }
  146. // check for exclusion
  147. if (!empty($this->currentNesting)) {
  148. for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
  149. $node = $this->currentNesting[$i];
  150. $def = $this->htmlDefinition->info[$node->name];
  151. if (isset($def->excludes[$name])) {
  152. return false;
  153. }
  154. }
  155. }
  156. return true;
  157. }
  158. /**
  159. * Iterator function, which starts with the next token and continues until
  160. * you reach the end of the input tokens.
  161. * @warning Please prevent previous references from interfering with this
  162. * functions by setting $i = null beforehand!
  163. * @param int $i Current integer index variable for inputTokens
  164. * @param HTMLPurifier_Token $current Current token variable.
  165. * Do NOT use $token, as that variable is also a reference
  166. * @return bool
  167. */
  168. protected function forward(&$i, &$current)
  169. {
  170. if ($i === null) {
  171. $i = count($this->inputZipper->back) - 1;
  172. } else {
  173. $i--;
  174. }
  175. if ($i < 0) {
  176. return false;
  177. }
  178. $current = $this->inputZipper->back[$i];
  179. return true;
  180. }
  181. /**
  182. * Similar to _forward, but accepts a third parameter $nesting (which
  183. * should be initialized at 0) and stops when we hit the end tag
  184. * for the node $this->inputIndex starts in.
  185. * @param int $i Current integer index variable for inputTokens
  186. * @param HTMLPurifier_Token $current Current token variable.
  187. * Do NOT use $token, as that variable is also a reference
  188. * @param int $nesting
  189. * @return bool
  190. */
  191. protected function forwardUntilEndToken(&$i, &$current, &$nesting)
  192. {
  193. $result = $this->forward($i, $current);
  194. if (!$result) {
  195. return false;
  196. }
  197. if ($nesting === null) {
  198. $nesting = 0;
  199. }
  200. if ($current instanceof HTMLPurifier_Token_Start) {
  201. $nesting++;
  202. } elseif ($current instanceof HTMLPurifier_Token_End) {
  203. if ($nesting <= 0) {
  204. return false;
  205. }
  206. $nesting--;
  207. }
  208. return true;
  209. }
  210. /**
  211. * Iterator function, starts with the previous token and continues until
  212. * you reach the beginning of input tokens.
  213. * @warning Please prevent previous references from interfering with this
  214. * functions by setting $i = null beforehand!
  215. * @param int $i Current integer index variable for inputTokens
  216. * @param HTMLPurifier_Token $current Current token variable.
  217. * Do NOT use $token, as that variable is also a reference
  218. * @return bool
  219. */
  220. protected function backward(&$i, &$current)
  221. {
  222. if ($i === null) {
  223. $i = count($this->inputZipper->front) - 1;
  224. } else {
  225. $i--;
  226. }
  227. if ($i < 0) {
  228. return false;
  229. }
  230. $current = $this->inputZipper->front[$i];
  231. return true;
  232. }
  233. /**
  234. * Handler that is called when a text token is processed
  235. */
  236. public function handleText(&$token)
  237. {
  238. }
  239. /**
  240. * Handler that is called when a start or empty token is processed
  241. */
  242. public function handleElement(&$token)
  243. {
  244. }
  245. /**
  246. * Handler that is called when an end token is processed
  247. */
  248. public function handleEnd(&$token)
  249. {
  250. $this->notifyEnd($token);
  251. }
  252. /**
  253. * Notifier that is called when an end token is processed
  254. * @param HTMLPurifier_Token $token Current token variable.
  255. * @note This differs from handlers in that the token is read-only
  256. * @deprecated
  257. */
  258. public function notifyEnd($token)
  259. {
  260. }
  261. }
  262. // vim: et sw=4 sts=4