From 010efc9b814b433bc60353caec185d905688a32b Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Tue, 5 Jun 2012 21:52:21 +0400 Subject: Revert "remove htmlpurifier" This reverts commit c21a462d52bd32737c32c29b060da03b38f1c2e6. --- .../HTMLPurifier/Strategy/MakeWellFormed.php | 532 +++++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 lib/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php (limited to 'lib/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php') diff --git a/lib/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php b/lib/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php new file mode 100644 index 000000000..c7aa1bb86 --- /dev/null +++ b/lib/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php @@ -0,0 +1,532 @@ +getHTMLDefinition(); + + // local variables + $generator = new HTMLPurifier_Generator($config, $context); + $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + // used for autoclose early abortion + $global_parent_allowed_elements = array(); + if (isset($definition->info[$definition->info_parent])) { + // may be unset under testing circumstances + $global_parent_allowed_elements = $definition->info[$definition->info_parent]->child->getAllowedElements($config); + } + $e = $context->get('ErrorCollector', true); + $t = false; // token index + $i = false; // injector index + $token = false; // the current token + $reprocess = false; // whether or not to reprocess the same token + $stack = array(); + + // member variables + $this->stack =& $stack; + $this->t =& $t; + $this->tokens =& $tokens; + $this->config = $config; + $this->context = $context; + + // context variables + $context->register('CurrentNesting', $stack); + $context->register('InputIndex', $t); + $context->register('InputTokens', $tokens); + $context->register('CurrentToken', $token); + + // -- begin INJECTOR -- + + $this->injectors = array(); + + $injectors = $config->getBatch('AutoFormat'); + $def_injectors = $definition->info_injector; + $custom_injectors = $injectors['Custom']; + unset($injectors['Custom']); // special case + foreach ($injectors as $injector => $b) { + // XXX: Fix with a legitimate lookup table of enabled filters + if (strpos($injector, '.') !== false) continue; + $injector = "HTMLPurifier_Injector_$injector"; + if (!$b) continue; + $this->injectors[] = new $injector; + } + foreach ($def_injectors as $injector) { + // assumed to be objects + $this->injectors[] = $injector; + } + foreach ($custom_injectors as $injector) { + if (!$injector) continue; + if (is_string($injector)) { + $injector = "HTMLPurifier_Injector_$injector"; + $injector = new $injector; + } + $this->injectors[] = $injector; + } + + // give the injectors references to the definition and context + // variables for performance reasons + foreach ($this->injectors as $ix => $injector) { + $error = $injector->prepare($config, $context); + if (!$error) continue; + array_splice($this->injectors, $ix, 1); // rm the injector + trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING); + } + + // -- end INJECTOR -- + + // a note on reprocessing: + // In order to reduce code duplication, whenever some code needs + // to make HTML changes in order to make things "correct", the + // new HTML gets sent through the purifier, regardless of its + // status. This means that if we add a start token, because it + // was totally necessary, we don't have to update nesting; we just + // punt ($reprocess = true; continue;) and it does that for us. + + // isset is in loop because $tokens size changes during loop exec + for ( + $t = 0; + $t == 0 || isset($tokens[$t - 1]); + // only increment if we don't need to reprocess + $reprocess ? $reprocess = false : $t++ + ) { + + // check for a rewind + if (is_int($i) && $i >= 0) { + // possibility: disable rewinding if the current token has a + // rewind set on it already. This would offer protection from + // infinite loop, but might hinder some advanced rewinding. + $rewind_to = $this->injectors[$i]->getRewind(); + if (is_int($rewind_to) && $rewind_to < $t) { + if ($rewind_to < 0) $rewind_to = 0; + while ($t > $rewind_to) { + $t--; + $prev = $tokens[$t]; + // indicate that other injectors should not process this token, + // but we need to reprocess it + unset($prev->skip[$i]); + $prev->rewind = $i; + if ($prev instanceof HTMLPurifier_Token_Start) array_pop($this->stack); + elseif ($prev instanceof HTMLPurifier_Token_End) $this->stack[] = $prev->start; + } + } + $i = false; + } + + // handle case of document end + if (!isset($tokens[$t])) { + // kill processing if stack is empty + if (empty($this->stack)) break; + + // peek + $top_nesting = array_pop($this->stack); + $this->stack[] = $top_nesting; + + // send error [TagClosedSuppress] + if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); + } + + // append, don't splice, since this is the end + $tokens[] = new HTMLPurifier_Token_End($top_nesting->name); + + // punt! + $reprocess = true; + continue; + } + + $token = $tokens[$t]; + + //echo '
'; printTokens($tokens, $t); printTokens($this->stack); + //flush(); + + // quick-check: if it's not a tag, no need to process + if (empty($token->is_tag)) { + if ($token instanceof HTMLPurifier_Token_Text) { + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) continue; + if ($token->rewind !== null && $token->rewind !== $i) continue; + $injector->handleText($token); + $this->processToken($token, $i); + $reprocess = true; + break; + } + } + // another possibility is a comment + continue; + } + + if (isset($definition->info[$token->name])) { + $type = $definition->info[$token->name]->child->type; + } else { + $type = false; // Type is unknown, treat accordingly + } + + // quick tag checks: anything that's *not* an end tag + $ok = false; + if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) { + // claims to be a start tag but is empty + $token = new HTMLPurifier_Token_Empty($token->name, $token->attr, $token->line, $token->col, $token->armor); + $ok = true; + } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { + // claims to be empty but really is a start tag + $this->swap(new HTMLPurifier_Token_End($token->name)); + $this->insertBefore(new HTMLPurifier_Token_Start($token->name, $token->attr, $token->line, $token->col, $token->armor)); + // punt (since we had to modify the input stream in a non-trivial way) + $reprocess = true; + continue; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // real empty token + $ok = true; + } elseif ($token instanceof HTMLPurifier_Token_Start) { + // start tag + + // ...unless they also have to close their parent + if (!empty($this->stack)) { + + // Performance note: you might think that it's rather + // inefficient, recalculating the autoclose information + // for every tag that a token closes (since when we + // do an autoclose, we push a new token into the + // stream and then /process/ that, before + // re-processing this token.) But this is + // necessary, because an injector can make an + // arbitrary transformations to the autoclosing + // tokens we introduce, so things may have changed + // in the meantime. Also, doing the inefficient thing is + // "easy" to reason about (for certain perverse definitions + // of "easy") + + $parent = array_pop($this->stack); + $this->stack[] = $parent; + + if (isset($definition->info[$parent->name])) { + $elements = $definition->info[$parent->name]->child->getAllowedElements($config); + $autoclose = !isset($elements[$token->name]); + } else { + $autoclose = false; + } + + if ($autoclose && $definition->info[$token->name]->wrap) { + // Check if an element can be wrapped by another + // element to make it valid in a context (for + // example,