Clean the hrefs of script and data embeds

@param \DOMElement $element
346204
protected function cleanHrefs(\DOMElement $element)
    {
        $href = $element->getAttribute('href');
        if (preg_match(self::SCRIPT_REGEX, $href) === 1) {
            $element->removeAttribute('href');
            $this->xmlIssues[] = array(
                'message' => 'Suspicious attribute \'href\'',
                'line' => $element->getLineNo(),
            );
        }
    }
689697
private function cleanup($html)
    {
        $html = trim($html);

        // special replacements (not properly handled by washtml class)
        $html_search = array(
            // space(s) between <NOBR>
            '/(<\/nobr>)(\s+)(<nobr>)/i',
            // PHP bug #32547 workaround: remove title tag
            '/<title[^>]*>[^<]*<\/title>/i',
            // remove <!doctype> before BOM (#1490291)
            '/<\!doctype[^>]+>[^<]*/im',
            // byte-order mark (only outlook?)
            '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/',
            // washtml/DOMDocument cannot handle xml namespaces
            '/<html\s[^>]+>/i',
        );

        $html_replace = array(
            '\\1'.' &nbsp; '.'\\3',
            '',
            '',
            '',
            '<html>',
        );

        $html = preg_replace($html_search, $html_replace, trim($html));

        // Replace all of those weird MS Word quotes and other high characters
        $badwordchars = array(
            "\xe2\x80\x98", // left single quote
            "\xe2\x80\x99", // right single quote
            "\xe2\x80\x9c", // left double quote
            "\xe2\x80\x9d", // right double quote
            "\xe2\x80\x94", // em dash
            "\xe2\x80\xa6"  // elipses
        );

        $fixedwordchars = array(
            "'",
            "'",
            '"',
            '"',
            '&mdash;',
            '...'
        );

        $html = str_replace($badwordchars, $fixedwordchars, $html);

        // PCRE errors handling (#1486856), should we use something like for every preg_* use?
        if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
            $errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";

            if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
                $errstr .= " Consider raising pcre.backtrack_limit!";
            }
            if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
                $errstr .= " Consider raising pcre.recursion_limit!";
            }

            rcube::raise_error(array('code' => 620, 'type' => 'php',
                'line' => __LINE__, 'file' => __FILE__,
                'message' => $errstr), true, false);

            return '';
        }

        // FIXME: HTML comments handling could be better. The code below can break comments (#6464),
        //        we should probably do not modify content inside comments at all.

        // fix (unknown/malformed) HTML tags before "wash"
        $html = preg_replace_callback('/(<(?!\!)[\/]*)([^\s>]+)([^>]*)/', array($this, 'html_tag_callback'), $html);

        // Remove invalid HTML comments (#1487759)
        // Note: We don't want to remove valid comments, conditional comments
        // and MSOutlook comments (<!-->)
        $html = preg_replace('/<!--[a-zA-Z0-9]+>/', '', $html);

        // fix broken nested lists
        self::fix_broken_lists($html);

        // turn relative into absolute urls
        $html = self::resolve_base($html);

        return $html;
    }
1026287
public function prepArticle(DOMDocument $article)
    {
        $this->logger->info('[PrepArticle] Preparing final article...');

        $this->_cleanStyles($article);
        $this->_clean($article, 'style');

        // Check for data tables before we continue, to avoid removing items in
        // those tables, which will often be isolated even though they're
        // visually linked to other content-ful elements (text, images, etc.).
        $this->_markDataTables($article);

        // Clean out junk from the article content
        $this->_cleanConditionally($article, 'form');
        $this->_cleanConditionally($article, 'fieldset');
        $this->_clean($article, 'object');
        $this->_clean($article, 'embed');
        $this->_clean($article, 'h1');
        $this->_clean($article, 'footer');
        $this->_clean($article, 'link');
        $this->_clean($article, 'aside');

        // Clean out elements have "share" in their id/class combinations from final top candidates,
        // which means we don't remove the top candidates even they have "share".
        foreach ($article->childNodes as $child) {
            $this->_cleanMatchedNodes($child, '/share/i');
        }

        /*
         * If there is only one h2 and its text content substantially equals article title,
         * they are probably using it as a header and not a subheader,
         * so remove it since we already extract the title separately.
         */
        $h2 = $article->getElementsByTagName('h2');
        if ($h2->length === 1) {
            $lengthSimilarRate = (mb_strlen($h2->item(0)->textContent) - mb_strlen($this->getTitle())) / max(mb_strlen($this->getTitle()), 1);

            if (abs($lengthSimilarRate) < 0.5) {
                if ($lengthSimilarRate > 0) {
                    $titlesMatch = strpos($h2->item(0)->textContent, $this->getTitle()) !== false;
                } else {
                    $titlesMatch = strpos($this->getTitle(), $h2->item(0)->textContent) !== false;
                }
                if ($titlesMatch) {
                    $this->logger->info('[PrepArticle] Found title repeated in an H2 node, removing...');
                    $this->_clean($article, 'h2');
                }
            }
        }

        $this->_clean($article, 'iframe');
        $this->_clean($article, 'input');
        $this->_clean($article, 'textarea');
        $this->_clean($article, 'select');
        $this->_clean($article, 'button');
        $this->_cleanHeaders($article);

        // Do these last as the previous stuff may have removed junk
        // that will affect these
        $this->_cleanConditionally($article, 'table');
        $this->_cleanConditionally($article, 'ul');
        $this->_cleanConditionally($article, 'div');

        $this->_cleanExtraParagraphs($article);

        foreach (iterator_to_array($article->getElementsByTagName('br')) as $br) {
            $next = $br->nextSibling;
            if ($next && $next->nodeName === 'p') {
                $this->logger->debug('[PrepArticle] Removing br node next to a p node.');
                $br->parentNode->removeChild($br);
            }
        }

        // Remove single-cell tables
        foreach ($article->shiftingAwareGetElementsByTagName('table') as $table) {
            /** @var DOMNode $table */
            $tbody = $table->hasSingleTagInsideElement('tbody') ? $table->childNodes[0] : $table;
            if ($tbody->hasSingleTagInsideElement('tr')) {
                $row = $tbody->firstChild;
                if ($row->hasSingleTagInsideElement('td')) {
                    $cell = $row->firstChild;
                    $cell = NodeUtility::setNodeTag($cell, (array_reduce(iterator_to_array($cell->childNodes), function ($carry, $node) {
                        return $node->isPhrasingContent() && $carry;
                    }, true)) ? 'p' : 'div');
                    $table->parentNode->replaceChild($cell, $table);
                }
            }
        }

        return $article;
    }
177053
public function cleanStyles($e)
    {
        if (!\is_object($e)) {
            return;
        }

        $elems = $e->getElementsByTagName('*');

        foreach ($elems as $elem) {
            $elem->removeAttribute('style');
        }
    }
822412
function clean($tag) {
		if (isset($this->_tags[$tag])) {
			foreach ($this->_tags[$tag] as $k) { /** @var string $k */
				unset($this->_data[$k]);
			}
			unset($this->_tags[$tag]);
		}
	}
125235
protected function copyLinkAttributes(DOMElement $embed)
    {
        $link = $embed->parentNode;

        if ($link->hasAttribute('object_id')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'object_id', $link->getAttribute('object_id'));
        }

        if ($link->hasAttribute('node_id')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'node_id', $link->getAttribute('node_id'));
        }

        if ($link->hasAttribute('anchor_name')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'anchor_name', $link->getAttribute('anchor_name'));
        }

        if ($link->hasAttribute('target')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'target', $link->getAttribute('target'));
        }

        if ($link->hasAttribute('xhtml:title')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'title', $link->getAttribute('xhtml:title'));
        }

        if ($link->hasAttribute('xhtml:id')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'id', $link->getAttribute('xhtml:id'));
        }

        if ($link->hasAttribute('class')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'class', $link->getAttribute('class'));
        }

        if ($link->hasAttribute('url')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'url', $link->getAttribute('url'));
        }

        if ($link->hasAttribute('url_id')) {
            $embed->setAttribute(static::TEMP_PREFIX . 'url_id', $link->getAttribute('url_id'));
        }
    }
1190241
protected function customAttributesToDOM(\DOMElement $element) : void
    {
        foreach ($this->customAttributes as $key => $value) {
            $element->setAttribute($key, $value);
        }
    }
1262747
function( element )
					{
						var attrs = element.attributes;
						if ( attrs && !attrs.href && attrs.name )
							delete element.name;
						else if ( CKEDITOR.env.webkit && attrs.href && attrs.href.match( /file:\/\/\/[\S]+#/i ) )
							attrs.href = attrs.href.replace( /file:\/\/\/[^#]+/i,'' );
					}
831572
public function processElement(\DOMElement $element, ContextInterface $context)
    {
        // Create a property
        $propertyContext = $this->processProperty($element, $context);

        // Process the element in case it's an anonymous thing with no children
        if (!$element->childNodes->length) {
            $this->processChild($element, $context);
        }

        return $propertyContext;
    }
263336
protected function addRelAttribute(DOMElement $element)
	{
		$rel = $element->getAttribute('rel');
		if (preg_match('(\\S$)', $rel))
		{
			$rel .= ' ';
		}
		$rel .= 'noreferrer';

		$element->setAttribute('rel', $rel);
	}
92877
protected function cleanAttributesOnWhitelist(\DOMElement $element)
    {
        for ($x = $element->attributes->length - 1; $x >= 0; $x--) {
            // get attribute name
            $attrName = $element->attributes->item($x)->name;

            // Remove attribute if not in whitelist
            if (!in_array(strtolower($attrName), $this->allowedAttrs) && !$this->isAriaAttribute(strtolower($attrName)) && !$this->isDataAttribute(strtolower($attrName))) {

                $element->removeAttribute($attrName);
                $this->xmlIssues[] = array(
                    'message' => 'Suspicious attribute \'' . $attrName . '\'',
                    'line' => $element->getLineNo(),
		);
            }

            // Do we want to strip remote references?
            if($this->removeRemoteReferences) {
                // Remove attribute if it has a remote reference
                if (isset($element->attributes->item($x)->value) && $this->hasRemoteReference($element->attributes->item($x)->value)) {
                    $element->removeAttribute($attrName);
                    $this->xmlIssues[] = array(
                        'message' => 'Suspicious attribute \'' . $attrName . '\'',
                        'line' => $element->getLineNo(),
		    );
                }
            }
        }
    }
179260
public static function delete(array $element) {
    if ($element['#value']) {
      $file = File::load($element['#value']);
      $file->delete();

      // Clean up the file system if needed.
      $uri = $file->getFileUri();
      if (file_exists($uri)) {
        \Drupal::service('file_system')->unlink($uri);
      }
    }
  }
860000
protected function normalizeAttributes(PHPTAL_Dom_Element $element)
    {
        parent::normalizeAttributes($element);

        $attrs_by_qname = array();
        foreach ($element->getAttributeNodes() as $attrnode) {
            // safe, as there can't be two attrs with same qname
            $attrs_by_qname[$attrnode->getQualifiedName()] = $attrnode;
        }

	if (count($attrs_by_qname) > 1) {
		uksort($attrs_by_qname, array($this, 'compareQNames'));
		$element->setAttributeNodes(array_values($attrs_by_qname));
	}
    }
1119561
public function remove(DOMElement &$element)
    {
        if (empty($element->parentNode)) {
            throw new DOMException('The current DOMItem has no parent.');
        }

        // Remove the element
        $element->parentNode->removeChild($element);
    }
199222
private function cleanAttributes(DOMElement $elem)
    {
        $tagName = strtolower($elem->nodeName);
        $attributes = $elem->attributes;
        $attributesWhiteList = $this->config->WhiteListHtmlGlobalAttributes;
        $attributesFilterMap = array();
        $allowDataAttribute = in_array("data-*", $attributesWhiteList);
        $whiteListAttr = $this->config->getWhiteListAttr($tagName);
        foreach ($whiteListAttr as $key => $val) {
            if (is_string($val)) {
                $attributesWhiteList[] = $val;
            }
            if ($val instanceof Closure) {
                $attributesWhiteList[] = $key;
                $attributesFilterMap[$key] = $val;
            }
        }
        $index = $attributes->length;
        while (--$index >= 0) {
        /*
        foreach会有一个严重的的问题：
            href="JavaScript:alert("customAttr="xxx"");"
            这样嵌套的不合法的属性，会被解析出两个属性：
                1. href="JavaScript:alert("
                2. customAttr="xxx"
            但是foreach只能拿到一个。
        foreach ($elem->attributes->item() as $attrName => $domAttr) {
        */
            /* @var $domAttr DOMAttr */
            $domAttr = $attributes->item($index);
            $attrName = strtolower($domAttr->name);
            $attrValue = $domAttr->value;
            // 如果不在白名单attr中，而且允许data-*，且不是data-*，则删除
            if (!in_array($attrName, $attributesWhiteList) && $allowDataAttribute && (stripos($attrName, "data-") !== 0)) {
                $elem->removeAttribute($attrName);
            } else {
                if (isset($attributesFilterMap[$attrName])) {
                    $filteredAttrValue = $attributesFilterMap[$attrName]($attrValue);
                    if ($filteredAttrValue === false ) {
                        $elem->removeAttribute($attrName);
                    } else {
                        // https://stackoverflow.com/questions/25475936/updating-domattr-value-with-url-results-in-parameters-being-lost-unless-htmlenti
                        // Fix ampersands issue: https://github.com/lincanbin/White-HTML-Filter/issues/1
                        if ($filteredAttrValue !== $attrValue) {
                            $elem->setAttribute($attrName, $filteredAttrValue);
                        }
                    }
                } else {
                    $this->cleanAttrValue($domAttr);
                }
            }
        }
    }
257113
private static void cleanHeaders(Element e) {
        for (int headerIndex = 1; headerIndex < 7; headerIndex++) {
            Elements headers = getElementsByTag(e, "h" + headerIndex);
            for (Element header : headers) {
                if (getClassWeight(header) < 0
                        || getLinkDensity(header) > 0.33f) {
                    header.remove();
                }
            }
        }
    }
543172
public function _cleanHeaders(DOMDocument $article)
    {
        for ($headerIndex = 1; $headerIndex < 3; $headerIndex++) {
            $headers = $article->getElementsByTagName('h' . $headerIndex);
            /** @var $header DOMElement */
            foreach ($headers as $header) {
                $weight = 0;
                if ($this->configuration->getWeightClasses()) {
                    $weight = $header->getClassWeight();
                }

                if ($weight < 0) {
                    $this->logger->debug(sprintf('[PrepArticle] Removing H node with 0 or less weight. Content was: \'%s\'', substr($header->nodeValue, 0, 128)));

                    NodeUtility::removeNode($header);
                }
            }
        }
    }