Filter Attributes

Filters tag attributes for consistency and safety.

@used-by    Security::jsImgRemoval()
@used-by    Security::jsLinkRemoval()

@param    string $str

@return    string
498347
protected static function filterAttributes($str)
    {
        $out = '';
        if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches)) {
            foreach ($matches[ 0 ] as $match) {
                $out .= preg_replace('#/\*.*?\*/#s', '', $match);
            }
        }

        return $out;
    }
741815
function clean_string($str)
	{
		if ($this->_is_ascii($str) === FALSE)
		{
			$str = @iconv('UTF-8', 'UTF-8//IGNORE', $str);
		}

		return $str;
	}
724325
protected function _filterTag($tag)
    {
        // Parse the tag into:
        // 1. a starting delimiter (mandatory)
        // 2. a tag name (if available)
        // 3. a string of attributes (if available)
        // 4. an ending delimiter (if available)
        $isMatch = preg_match('~(</?)(\w*)((/(?!>)|[^/>])*)(/?>)~', $tag, $matches);

        // If the tag does not match, then strip the tag entirely
        if (!$isMatch) {
            return '';
        }

        // Save the matches to more meaningfully named variables
        $tagStart = $matches[1];
        $tagName = strtolower($matches[2]);
        $tagAttributes = $matches[3];
        $tagEnd = $matches[5];

        // If the tag is not an allowed tag, then remove the tag entirely
        if (!isset($this->_tagsAllowed[$tagName])) {
            return '';
        }

        // Trim the attribute string of whitespace at the ends
        $tagAttributes = trim($tagAttributes);

        // If there are non-whitespace characters in the attribute string
        if (strlen($tagAttributes)) {
            // Parse iteratively for well-formed attributes
            preg_match_all('/(\w+)\s*=\s*(?:(")(.*?)"|(\')(.*?)\')/s', $tagAttributes, $matches);

            // Initialize valid attribute accumulator
            $tagAttributes = '';

            // Iterate over each matched attribute
            foreach ($matches[1] as $index => $attributeName) {
                $attributeName = strtolower($attributeName);
                $attributeDelimiter = $matches[2][$index];
                $attributeValue = $matches[3][$index];

                // If the attribute is not allowed, then remove it entirely
                if (!array_key_exists($attributeName, $this->_tagsAllowed[$tagName]) && !array_key_exists($attributeName, $this->_attributesAllowed)) {
                    continue;
                }
                // Add the attribute to the accumulator
                $tagAttributes .= " $attributeName=" . $attributeDelimiter . $attributeValue . $attributeDelimiter;
            }
        }

        // Reconstruct tags ending with "/>" as backwards-compatible XHTML tag
        if (strpos($tagEnd, '/') !== false) {
            $tagEnd = " $tagEnd";
        }

        // Return the filtered tag
        return $tagStart . $tagName . $tagAttributes . $tagEnd;
    }
122875
public function token($str) {
        $str=trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
            Base::instance()->compile($str)));
        if (preg_match('/^(.+)(?<!\|)\|((?:\h*\w+(?:\h*[,;]?))+)$/s',
            $str,$parts)) {
            $str=trim($parts[1]);
            foreach (Base::instance()->split($parts[2]) as $func)
                $str=is_string($cmd=$this->filter($func))?
                    $cmd.'('.$str.')':
                    'Base::instance()->'.
                    'call($this->filter(\''.$func.'\'),['.$str.'])';
        } elseif (false !== strpos($str, '(')) {
            for ($i=0,$len=strlen($str),$newStr='',$tmp='';$i<$len;) {
                if ($str[$i] === '(') {
                    if ($tmp && array_key_exists($tmp, $this->filter)) {
                        $tmp = $this->filter[$tmp];
                    }
                    $newStr .= $tmp . '(';
                    $tmp = '';
                } elseif (in_array($str[$i], [' '])) {
                    $newStr .= $tmp . $str[$i];
                    $tmp = '';
                } else {
                    $tmp .= $str[$i];
                }
                $i++;
            }
            $newStr .= $tmp;
            $str = $newStr;
        }

        return $str;
    }
110946
public function parse($str)
	{
		if ($this->isQuoteHtml() === true)
		{
			$str = htmlspecialchars($str, ENT_QUOTES);
		}

		foreach ($this->getBbCodes() as $key => $val)
		{
			$str = preg_replace($key, $val, $str);
		}

		return $str;
	}
915957
private function _do($str)
    {
        $str = (string) $str;
        $strInt = (int) $str;
        $strFloat = (float) $str;
        if (
            !$str
            ||
            (string) $strInt === $str
            ||
            (string) $strFloat === $str
        ) {

            // no xss found
            if ($this->_xss_found !== true) {
                $this->_xss_found = false;
            }

            return $str;
        }

        // remove the BOM from UTF-8 / UTF-16 / UTF-32 strings
        $str = UTF8::remove_bom($str);

        // replace the diamond question mark (�) and invalid-UTF8 chars
        $str = UTF8::replace_diamond_question_mark($str, '');

        // replace invisible characters with one single space
        $str = UTF8::remove_invisible_characters($str, true, ' ');

        // normalize the whitespace
        $str = UTF8::normalize_whitespace($str);

        // decode UTF-7 characters
        $str = $this->_repack_utf7($str);

        // decode the string
        $str = $this->_decode_string($str);

        // remove all >= 4-Byte chars if needed
        if ($this->_stripe_4byte_chars) {
            $str = (string) \preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $str);
        }

        // backup the string (for later comparision)
        $str_backup = $str;

        // remove strings that are never allowed
        $str = $this->_do_never_allowed($str);

        // corrects words before the browser will do it
        $str = $this->_compact_exploded_javascript($str);

        // remove disallowed javascript calls in links, images etc.
        $str = $this->_remove_disallowed_javascript($str);

        // remove evil attributes such as style, onclick and xmlns
        $str = $this->_remove_evil_attributes($str);

        // sanitize naughty JavaScript elements
        $str = $this->_sanitize_naughty_javascript($str);

        // sanitize naughty HTML elements
        $str = $this->_sanitize_naughty_html($str);

        // final clean up
        //
        // -> This adds a bit of extra precaution in case something got through the above filters.
        $str = $this->_do_never_allowed_afterwards($str);

        // check for xss
        if ($this->_xss_found !== true) {
            $this->_xss_found = !($str_backup === $str);
        }

        return $str;
    }
613230
public static function filterBadProtocol($string) {
    // Get the plain text representation of the attribute value (i.e. its
    // meaning).
    $string = Html::decodeEntities($string);
    return Html::escape(static::stripDangerousProtocols($string));
  }
238804
public function safeEmailFilter($str)
    {
        $email   = '';
        for ($i = 0, $len = strlen($str); $i < $len; $i++) {
            $j = random_int(0, 1);

            $email .= $j === 0 ? '&#' . ord($str[$i]) . ';' : $str[$i];
        }

        return str_replace('@', '&#64;', $email);
    }
259168
public function filterTrace($str) {
		if (empty($this->_options['backtrace_filters'])) {
			return $str;
		}

		foreach ($this->_options['backtrace_filters'] as $pattern => $replacement) {
			$str = preg_replace($pattern, $replacement, $str);
		}

		return $str;
	}
953312
protected function modifyQuery(string $str): string
    {
        return (string) $this->filterQuery($str)->merge($this->query);
    }
115232
final public static function safeEncodeStr(string $str): string
    {
        return preg_replace_callback('/&#([a-z\d]+);/i', function ($m) {
            $value = (string)$m[0];
            $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
            return $value;
        }, $str);
    }
918575
public static function sanitizeJsVarName($str)
    {
        $out = preg_replace("/ /", '_', trim($str));
        $out = preg_replace("/[^A-Za-z_]/", '', $out);

        return $out;
    }
931584
function _filter_uri($str)
	{
		if ($str != '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') == FALSE)
		{
			// preg_quote() in PHP 5.3 escapes -, so the str_replace() and addition of - to preg_quote() is to maintain backwards
			// compatibility as many are unaware of how characters in the permitted_uri_chars will be parsed as a regex pattern
			if ( ! preg_match("|^[".str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-'))."]+$|i", $str))
			{
				show_error('The URI you submitted has disallowed characters.', 400);
			}
		}

		// Convert programatic characters to entities
		$bad	= array('$',		'(',		')',		'%28',		'%29');
		$good	= array('&#36;',	'&#40;',	'&#41;',	'&#40;',	'&#41;');

		return str_replace($bad, $good, $str);
	}
128914
protected function _extractAttributes($str) {
		$str = preg_replace("/[\x{00a0}\x{200b}]+/u", ' ', $str);
		$attributes = array();
		$patterns = array(
			'name-value-quotes-dble' => '(\w+)\s*=\s*"([^"]*)"',
			'name-value-quotes-sgle' => "(\w+)\s*=\s*'([^']*)'",
			'name-value-quotes-none' => '(\w+)\s*=\s*([^\s\'"]+)',
			'name-quotes-dble' => '"([^"]*)"',
			'name-quotes-sgle' => "'([^']*)'",
			'name-quotes-none' => '(\w+)'
		);
		$pattern = '/' . implode('(?:\s|$)|', $patterns) . '(?:\s|$)/';

		if (!preg_match_all($pattern, $str, $matches, PREG_SET_ORDER)) {
			return $attributes;
		}

		foreach ($matches as $match) {
			if (!empty($match[1])) {
				$attributes[strtolower($match[1])] = stripcslashes($match[2]);
			} else if (!empty($match[3])) {
				$attributes[$match[3]] = stripcslashes($match[4]);
			} else if (!empty($match[5])) {
				$attributes[$match[5]] = stripcslashes($match[6]);
			} else if (!empty($match[7]) && strlen($match[7])) {
				$attributes[] = stripcslashes($match[7]);
			} else if (!empty($match[8])) {
				$attributes[] = stripcslashes($match[8]);
			} else if (!empty($match[9])) {
				$attributes[] = stripcslashes($match[9]);
			}
		}

		return $attributes;
	}
130806
private function cleanInputData($str) {
        if (is_array($str)) {
            $new_array = [];
            foreach ($str as $key => $val) {
                $new_array [ $this->cleanInputKeys($key) ] = $this->cleanInputData($val);
            }

            return $new_array;
        }
        $str = self::$xss_cleaner->xss_clean($str);
        // Standardize newlines
        if (strpos($str, "\r") !== false) {
            $str = str_replace(["\r\n", "\r"], "\n", $str);
        }

        return $str;
    }
469846
protected function clean($str)
    {
        if ($this->options->get('cleanupInput') != true) {
            // skip entire cleanup step
            return $str;
        }

        // remove white space before closing tags
        $str = mb_eregi_replace("'\s+>", "'>", $str);
        $str = mb_eregi_replace('"\s+>', '">', $str);

        // clean out the \n\r
        $replace = ' ';
        if ($this->options->get('preserveLineBreaks')) {
            $replace = '&#10;';
        }
        $str = str_replace(["\r\n", "\r", "\n"], $replace, $str);

        // strip the doctype
        $str = mb_eregi_replace("<!doctype(.*?)>", '', $str);

        // strip out comments
        $str = mb_eregi_replace("<!--(.*?)-->", '', $str);

        // strip out cdata
        $str = mb_eregi_replace("<!\[CDATA\[(.*?)\]\]>", '', $str);

        // strip out <script> tags
        if ($this->options->get('removeScripts') == true) {
            $str = mb_eregi_replace("<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>", '', $str);
            $str = mb_eregi_replace("<\s*script\s*>(.*?)<\s*/\s*script\s*>", '', $str);
        }

        // strip out <style> tags
        if ($this->options->get('removeStyles') == true) {
            $str = mb_eregi_replace("<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>", '', $str);
            $str = mb_eregi_replace("<\s*style\s*>(.*?)<\s*/\s*style\s*>", '', $str);
        }

        // strip out server side scripts
        $str = mb_eregi_replace("(<\?)(.*?)(\?>)", '', $str);

        // strip smarty scripts
        $str = mb_eregi_replace("(\{\w)(.*?)(\})", '', $str);

        return $str;
    }
1346646
protected function parse($str)
    {
        $count = preg_match_all($this->tags->regex(), $str, $matches);

        // If we didn't find anything, return the string right back
        if (!$count || !is_array($matches)) {
            return $str;
        }

        // All we want is the array of actual strings matched
        $matches = reset($matches);

        return $this->parseTags($str, $matches);
    }