'message' => 'Suspicious attribute \'href\'', 'line' => $element->getLineNo(), ); } } /** * Clean the hrefs of script and data embeds * * @param \DOMElement $element */ protected function cleanHrefs(\DOMElement $element) { $href = $element->getAttribute('href'); if (false === $this->isHrefSafeValue($href)) { $element->removeAttribute('href'); $this->xmlIssues[] = array( 'message' => 'Suspicious attribute \'href\'', 'line' => $element->getLineNo(), ); } } /** * Only allow whitelisted starts to be within the href. * * This will stop scripts etc from being passed through, with or without attempting to hide bypasses. * This stops the need for us to use a complicated script regex. * * @param $value * @return bool */ protected function isHrefSafeValue($value) { // Allow empty values if (empty($value)) { return true; } // Allow fragment identifiers. if ('#' === substr($value, 0, 1)) { return true; } // Allow relative URIs. if ('/' === substr($value, 0, 1)) { return true; } // Allow HTTPS domains. if ('https://' === substr($value, 0, 8)) { return true; } // Allow HTTP domains. if ('http://' === substr($value, 0, 7)) { return true; } // Allow known data URIs. if (in_array(substr($value, 0, 14), array( 'data:image/png', // PNG 'data:image/gif', // GIF 'data:image/jpg', // JPG 'data:image/jpe', // JPEG 'data:image/pjp', // PJPEG ))) { return true; } // Allow known short data URIs. if (in_array(substr($value, 0, 12), array( 'data:img/png', // PNG 'data:img/gif', // GIF 'data:img/jpg', // JPG 'data:img/jpe', // JPEG 'data:img/pjp', // PJPEG ))) { return true; } return false; } /** * Removes non-printable ASCII characters from string & trims it * * @param string $value * @return bool */ protected function removeNonPrintableCharacters($value) { return trim(preg_replace('/[^ -~]/xu','',$value)); } /** * Does this attribute value have a remote reference? * * @param $value * @return bool */ protected function hasRemoteReference($value) { $value = $this->removeNonPrintableCharacters($value); $wrapped_in_url = preg_match('~^url\(\s*[\'"]\s*(.*)\s*[\'"]\s*\)$~xi', $value, $match); if (!$wrapped_in_url){ return false; } $value = trim($match[1], '\'"'); return preg_match('~^((https?|ftp|file):)?//~xi', $value); } /** * Should we minify the output? * * @param bool $shouldMinify */ public function minify($shouldMinify = false) { $this->minifyXML = (bool) $shouldMinify; } /** * Should we remove the XML tag in the header? * * @param bool $removeXMLTag */ public function removeXMLTag($removeXMLTag = false) { $this->removeXMLTag = (bool) $removeXMLTag; } /** * Whether `` elements shall be * removed in case expansion would exceed this threshold. * * @param int $useThreshold */ public function useThreshold($useThreshold = 1000) { $this->useThreshold = (int)$useThreshold; } /** * Check to see if an attribute is an aria attribute or not * * @param $attributeName * * @return bool */ protected function isAriaAttribute($attributeName) { return strpos($attributeName, 'aria-') === 0; } /** * Check to see if an attribute is an data attribute or not * * @param $attributeName * * @return bool */ protected function isDataAttribute($attributeName) { return strpos($attributeName, 'data-') === 0; } /** * Make sure our use tag is only referencing internal resources * * @param \DOMElement $element * @return bool */ protected function isUseTagDirty(\DOMElement $element) { $href = Helper::getElementHref($element); return $href && strpos($href, '#') !== 0; } /** * Determines whether `` is expanded * recursively in order to create DoS scenarios. The amount of a actually * used element needs to be below `$this->useThreshold`. * * @param \DOMElement $element * @return bool */ protected function isUseTagExceedingThreshold(\DOMElement $element) { if ($this->useThreshold <= 0) { return false; } $useId = Helper::extractIdReferenceFromHref( Helper::getElementHref($element) ); if ($useId === null) { return false; } foreach ($this->elementReferenceResolver->findByElementId($useId) as $subject) { if ($subject->countUse() >= $this->useThreshold) { return true; } } return false; } /** * Set the nesting limit for tags. * * @param $limit */ public function setUseNestingLimit($limit) { $this->useNestingLimit = (int) $limit; } }