• Модуль: main
  • Путь к файлу: ~/bitrix/modules/main/classes/general/sanitizer.php
  • Класс: CBXSanitizer
  • Вызов: CBXSanitizer::SanitizeHtml
public function SanitizeHtml($html)
	{
		if(empty($this->arHtmlTags))
			$this->SetLevel(self::SECURE_LEVEL_HIGH);

		$openTagsStack = array();
		$isCode = false;

		$seg = $this->splitHtml($html);

		//process segments
		$segCount = count($seg);
		for($i=0; $i<$segCount; $i++)
		{
			if($seg[$i]['segType'] == 'text')
			{
				if (trim($seg[$i]['value']) && ($tp = array_search('table', $openTagsStack)) !== false)
				{
					$cellTags = array_intersect(array('td', 'th'), array_keys($this->arHtmlTags));
					if ($cellTags && !array_intersect($cellTags, array_slice($openTagsStack, $tp+1)))
					{
						array_splice($seg, $i, 0, array(array('segType' => 'tag', 'value' => sprintf('<%s>', reset($cellTags)))));
						$i--; $segCount++;

						continue;
					}
				}

				if ($this->bHtmlSpecChars)
				{
					$openTagsStackSize = count($openTagsStack);
					$entQuotes = ($openTagsStackSize && $openTagsStack[$openTagsStackSize-1] === 'style' ? ENT_NOQUOTES : ENT_QUOTES);

					$seg[$i]['value'] = htmlspecialchars(
						$seg[$i]['value'],
						$entQuotes,
						LANG_CHARSET,
						$this->bDoubleEncode
					);
				}
			}
			elseif(
				$seg[$i]['segType'] == 'tag'
				&& (
					preg_match('/^$/', $seg[$i]['value'])
				)
			)
			{
				//Keep ms html comments https://stackoverflow.design/email/base/mso/
				$seg[$i]['segType'] = 'text';
			}
			elseif($seg[$i]['segType'] == 'tag')
			{
				//find tag type (open/close), tag name, attributies
				preg_match('#^$#si'.BX_UTF_PCRE_MODIFIER, $seg[$i]['value'], $matches);
				$seg[$i]['tagType'] = !empty($matches[1]) ? 'close' : 'open';
				$seg[$i]['tagName'] = mb_strtolower($matches[2] ?? '');

				if(($seg[$i]['tagName']=='code') && ($seg[$i]['tagType']=='close'))
					$isCode = false;

				//if tag founded inside    it is simple text
				if($isCode)
				{
					$seg[$i]['segType'] = 'text';
					$i--;
					continue;
				}

				if($seg[$i]['tagType'] == 'open')
				{
					// if tag unallowed screen it, or erase
					if(!array_key_exists($seg[$i]['tagName'], $this->arHtmlTags))
					{
						if($this->bDelSanitizedTags)
						{
							$seg[$i]['action'] = self::ACTION_DEL;
						}
						else
						{
							$seg[$i]['segType'] = 'text';
							$i--;
							continue;
						}
					}
					//if allowed
					else
					{
						if (in_array('table', $openTagsStack))
						{
							if ($openTagsStack[count($openTagsStack)-1] == 'table')
							{
								if (array_key_exists('tr', $this->arHtmlTags) && !in_array($seg[$i]['tagName'], array('thead', 'tfoot', 'tbody', 'tr')))
								{
									array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'open', 'tagName' => 'tr', 'action' => self::ACTION_ADD)));
									$i++; $segCount++;

									$openTagsStack[] = 'tr';
								}
							}

							if (in_array($openTagsStack[count($openTagsStack)-1], array('thead', 'tfoot', 'tbody')))
							{
								if (array_key_exists('tr', $this->arHtmlTags) && $seg[$i]['tagName'] != 'tr')
								{
									array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'open', 'tagName' => 'tr', 'action' => self::ACTION_ADD)));
									$i++; $segCount++;

									$openTagsStack[] = 'tr';
								}
							}

							if ($seg[$i]['tagName'] == 'tr')
							{
								for ($j = count($openTagsStack)-1; $j >= 0; $j--)
								{
									if (in_array($openTagsStack[$j], array('table', 'thead', 'tfoot', 'tbody')))
										break;

									array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'close', 'tagName' => $openTagsStack[$j], 'action' => self::ACTION_ADD)));
									$i++; $segCount++;

									array_splice($openTagsStack, $j, 1);
								}
							}

							if ($openTagsStack[count($openTagsStack)-1] == 'tr')
							{
								$cellTags = array_intersect(array('td', 'th'), array_keys($this->arHtmlTags));
								if ($cellTags && !in_array($seg[$i]['tagName'], $cellTags))
								{
									array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'open', 'tagName' => reset($cellTags), 'action' => self::ACTION_ADD)));
									$i++; $segCount++;

									$openTagsStack[] = 'td';
								}
							}

							if (in_array($seg[$i]['tagName'], array('td', 'th')))
							{
								for ($j = count($openTagsStack)-1; $j >= 0; $j--)
								{
									if ($openTagsStack[$j] == 'tr')
										break;

									array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'close', 'tagName' => $openTagsStack[$j], 'action' => self::ACTION_ADD)));
									$i++; $segCount++;

									array_splice($openTagsStack, $j, 1);
								}
							}
						}

						//Processing valid tables
						//if find 'tr','td', etc...
						if(array_key_exists($seg[$i]['tagName'], $this->arTableTags))
						{
							$this->CleanTable($seg, $openTagsStack, $i, false);

							if(isset($seg[$i]['action']) && $seg[$i]['action'] == self::ACTION_DEL)
								continue;
						}

						$seg[$i]['attr'] = $this->processAttributes(
							(string)$matches[3], //attributes string
							(string)$seg[$i]['tagName']
						);

						if($seg[$i]['tagName'] === 'code')
						{
							$isCode = true;
						}

						//if tag need close tag add it to stack opened tags
						if(!in_array($seg[$i]['tagName'], $this->arNoClose)) //!count($this->arHtmlTags[$seg[$i]['tagName']]) || fix: 
{ $openTagsStack[] = $seg[$i]['tagName']; $seg[$i]['closeIndex'] = count($openTagsStack)-1; } } } //if closing tag else { //if tag allowed if(array_key_exists($seg[$i]['tagName'], $this->arHtmlTags) && (!count($this->arHtmlTags[$seg[$i]['tagName']]) || ($this->arHtmlTags[$seg[$i]['tagName']][count($this->arHtmlTags[$seg[$i]['tagName']])-1] != false))) { if($seg[$i]['tagName'] == 'code') { $isCode = false; } //if open tags stack is empty, or not include it's name lets screen/erase it if((empty($openTagsStack)) || (!in_array($seg[$i]['tagName'], $openTagsStack))) { if($this->bDelSanitizedTags || $this->arNoClose) { $seg[$i]['action'] = self::ACTION_DEL; } else { $seg[$i]['segType'] = 'text'; $i--; continue; } } else { //if this tag don't match last from open tags stack , adding right close tag $tagName = array_pop($openTagsStack); if($seg[$i]['tagName'] != $tagName) { array_splice($seg, $i, 0, array(array('segType'=>'tag', 'tagType'=>'close', 'tagName'=>$tagName, 'action'=>self::ACTION_ADD))); $segCount++; } } } //if tag unallowed erase it else { if($this->bDelSanitizedTags) { $seg[$i]['action'] = self::ACTION_DEL; } else { $seg[$i]['segType'] = 'text'; $i--; continue; } } } } } //close tags stayed in stack foreach(array_reverse($openTagsStack) as $val) array_push($seg, array('segType'=>'tag', 'tagType'=>'close', 'tagName'=>$val, 'action'=>self::ACTION_ADD)); //build filtered code and return it $filteredHTML = ''; $flagDeleteContent = false; foreach($seg as $segt) { if(($segt['action'] ?? '') != self::ACTION_DEL && !$flagDeleteContent) { if($segt['segType'] == 'text') { $filteredHTML .= $segt['value']; } elseif($segt['segType'] == 'tag') { if($segt['tagType'] == 'open') { $filteredHTML .= '<'.$segt['tagName']; if(isset($segt['attr']) && is_array($segt['attr'])) foreach($segt['attr'] as $attr_key => $attr_val) $filteredHTML .= ' '.$attr_key.'="'.$attr_val.'"'; if (count($this->arHtmlTags[$segt['tagName']]) && ($this->arHtmlTags[$segt['tagName']][count($this->arHtmlTags[$segt['tagName']])-1] == false)) $filteredHTML .= " /"; $filteredHTML .= '>'; } elseif($segt['tagType'] == 'close') $filteredHTML .= ''; } } else { if(in_array($segt['tagName'], $this->delTagsWithContent)) { $flagDeleteContent = $segt['tagType'] == 'open'; } } } if(!$this->bHtmlSpecChars && $html != $filteredHTML) { $filteredHTML = $this->SanitizeHtml($filteredHTML); } return $filteredHTML; }