138 if(!is_array($arTags))
142 $this->secLevel = self::SECURE_LEVEL_CUSTOM;
144 foreach($arTags as $tagName => $arAttrs)
146 $tagName = mb_strtolower($tagName);
147 $arAttrs = array_change_key_case($arAttrs, CASE_LOWER);
148 $this->arHtmlTags[$tagName] = $arAttrs;
412 if (!isset($arAttr[1]) || !isset($arAttr[3]))
417 $attr = mb_strtolower($arAttr[1]);
418 $attrValue = $this->
Decode($arAttr[3]);
425 if(!preg_match(
"#^(http://|https://|ftp://|file://|mailto:|callto:|skype:|tel:|sms:|\\#|/)#iu", $attrValue))
427 $arAttr[3] =
'http://' . $arAttr[3];
429 $valid = (!preg_match(
"#javascript:|data:|[^\\w".$this->localAlph.
"a-zA-Z:/\\.=@;,!~\\*\\&\\#\\)(%\\s\\+\$\\?\\-\\[\\]]#iu", $attrValue))
437 $valid = !preg_match(
"#^[^0-9\\-]+(px|%|\\*)*#iu", $attrValue)
443 $valid = !preg_match(
"#[^\\w".$this->localAlph.
"\\.\\?!,:;\\s\\-]#iu", $attrValue)
448 $attrValue = str_replace(
'"',
'', $attrValue);
449 $valid = !preg_match(
"#(behavior|expression|javascript)#iu", $attrValue) && !preg_match(
"#[^\\/\\w\\s)(!%,:\\.;\\-\\#\\']#iu", $attrValue)
454 $valid = !preg_match(
"#[^0-9\\s,\\-]#iu", $attrValue)
459 if (array_key_exists($attr, $this->additionalAttrs))
461 $valid =
true === call_user_func_array(
462 $this->additionalAttrs[$attr][
'content'],
468 $valid = !preg_match(
"#[^\\s\\w" . $this->localAlph .
"\\-\\#\\.\/;]#iu", $attrValue)
548 $Sanitizer =
new self;
550 if(empty(self::$arOldTags))
551 $Sanitizer->SetLevel(self::SECURE_LEVEL_HIGH);
554 $Sanitizer->DelAllTags();
555 $Sanitizer->AddTags(self::$arOldTags);
558 $Sanitizer->ApplyHtmlSpecChars($htmlspecialchars);
559 $Sanitizer->DeleteSanitizedTags($delTags);
560 $Sanitizer->ApplyDoubleEncode();
562 return $Sanitizer->SanitizeHtml($html);
600 if(empty($this->arHtmlTags))
601 $this->
SetLevel(self::SECURE_LEVEL_HIGH);
603 $openTagsStack =
array();
609 $segCount =
count($seg);
610 for(
$i=0;
$i<$segCount;
$i++)
612 if($seg[
$i][
'segType'] ==
'text')
614 if (trim($seg[
$i][
'value']) && ($tp = array_search(
'table', $openTagsStack)) !==
false)
616 $cellTags = array_intersect(
array(
'td',
'th'), array_keys($this->arHtmlTags));
617 if ($cellTags && !array_intersect($cellTags, array_slice($openTagsStack, $tp+1)))
619 array_splice($seg,
$i, 0,
array(
array(
'segType' =>
'tag',
'value' => sprintf(
'<%s>', reset($cellTags)))));
626 if ($this->bHtmlSpecChars)
628 $openTagsStackSize =
count($openTagsStack);
629 $entQuotes = ($openTagsStackSize && $openTagsStack[$openTagsStackSize-1] ===
'style' ? ENT_NOQUOTES : ENT_QUOTES);
631 $seg[
$i][
'value'] = htmlspecialchars(
640 $seg[
$i][
'segType'] ==
'tag'
642 preg_match(
'/^<!--\\[if\\s+((?:mso|gt|lt|gte|lte|\\||!|[0-9]+|\\(|\\))\\s*)+\\]>$/', $seg[
$i][
'value'])
643 || preg_match(
'/^<!\\[endif\\]-->$/', $seg[
$i][
'value'])
648 $seg[
$i][
'segType'] =
'text';
650 elseif($seg[
$i][
'segType'] ==
'tag')
653 preg_match(
'#^<\s*(/)?\s*([a-z0-9]+)(.*?)>$#siu', $seg[
$i][
'value'],
$matches);
654 $seg[
$i][
'tagType'] = !empty(
$matches[1]) ?
'close' :
'open';
655 $seg[
$i][
'tagName'] = mb_strtolower(
$matches[2] ??
'');
657 if(($seg[
$i][
'tagName']==
'code') && ($seg[
$i][
'tagType']==
'close'))
663 $seg[
$i][
'segType'] =
'text';
668 if($seg[
$i][
'tagType'] ==
'open')
671 if(!array_key_exists($seg[
$i][
'tagName'], $this->arHtmlTags))
673 if($this->bDelSanitizedTags)
675 $seg[
$i][
'action'] = self::ACTION_DEL;
679 $seg[
$i][
'segType'] =
'text';
687 if (in_array(
'table', $openTagsStack))
689 if ($openTagsStack[
count($openTagsStack)-1] ==
'table')
691 if (array_key_exists(
'tr', $this->arHtmlTags) && !in_array($seg[
$i][
'tagName'],
array(
'thead',
'tfoot',
'tbody',
'tr')))
693 array_splice($seg,
$i, 0,
array(
array(
'segType' =>
'tag',
'tagType' =>
'open',
'tagName' =>
'tr',
'action' => self::ACTION_ADD)));
696 $openTagsStack[] =
'tr';
700 if (in_array($openTagsStack[
count($openTagsStack)-1],
array(
'thead',
'tfoot',
'tbody')))
702 if (array_key_exists(
'tr', $this->arHtmlTags) && $seg[
$i][
'tagName'] !=
'tr')
704 array_splice($seg,
$i, 0,
array(
array(
'segType' =>
'tag',
'tagType' =>
'open',
'tagName' =>
'tr',
'action' => self::ACTION_ADD)));
707 $openTagsStack[] =
'tr';
711 if ($seg[
$i][
'tagName'] ==
'tr')
713 for ($j =
count($openTagsStack)-1; $j >= 0; $j--)
715 if (in_array($openTagsStack[$j],
array(
'table',
'thead',
'tfoot',
'tbody')))
718 array_splice($seg,
$i, 0,
array(
array(
'segType' =>
'tag',
'tagType' =>
'close',
'tagName' => $openTagsStack[$j],
'action' => self::ACTION_ADD)));
721 array_splice($openTagsStack, $j, 1);
725 if ($openTagsStack[
count($openTagsStack)-1] ==
'tr')
727 $cellTags = array_intersect(
array(
'td',
'th'), array_keys($this->arHtmlTags));
728 if ($cellTags && !in_array($seg[
$i][
'tagName'], $cellTags))
730 array_splice($seg,
$i, 0,
array(
array(
'segType' =>
'tag',
'tagType' =>
'open',
'tagName' => reset($cellTags),
'action' => self::ACTION_ADD)));
733 $openTagsStack[] =
'td';
737 if (in_array($seg[
$i][
'tagName'],
array(
'td',
'th')))
739 for ($j =
count($openTagsStack)-1; $j >= 0; $j--)
741 if ($openTagsStack[$j] ==
'tr')
744 array_splice($seg,
$i, 0,
array(
array(
'segType' =>
'tag',
'tagType' =>
'close',
'tagName' => $openTagsStack[$j],
'action' => self::ACTION_ADD)));
747 array_splice($openTagsStack, $j, 1);
754 if(array_key_exists($seg[
$i][
'tagName'], $this->arTableTags))
758 if(isset($seg[
$i][
'action']) && $seg[
$i][
'action'] == self::ACTION_DEL)
764 (
string)$seg[
$i][
'tagName']
767 if($seg[
$i][
'tagName'] ===
'code')
773 if(!in_array($seg[
$i][
'tagName'], $this->arNoClose))
775 $openTagsStack[] = $seg[
$i][
'tagName'];
776 $seg[
$i][
'closeIndex'] =
count($openTagsStack)-1;
783 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)))
785 if($seg[
$i][
'tagName'] ==
'code')
790 if((empty($openTagsStack)) || (!in_array($seg[
$i][
'tagName'], $openTagsStack)))
792 if($this->bDelSanitizedTags || $this->arNoClose)
794 $seg[
$i][
'action'] = self::ACTION_DEL;
798 $seg[
$i][
'segType'] =
'text';
806 $tagName = array_pop($openTagsStack);
807 if($seg[
$i][
'tagName'] != $tagName)
809 array_splice($seg,
$i, 0,
array(
array(
'segType'=>
'tag',
'tagType'=>
'close',
'tagName'=>$tagName,
'action'=>self::ACTION_ADD)));
817 if($this->bDelSanitizedTags)
819 $seg[
$i][
'action'] = self::ACTION_DEL;
823 $seg[
$i][
'segType'] =
'text';
833 foreach(array_reverse($openTagsStack) as
$val)
834 array_push($seg,
array(
'segType'=>
'tag',
'tagType'=>
'close',
'tagName'=>
$val,
'action'=>self::ACTION_ADD));
838 $flagDeleteContent =
false;
840 foreach($seg as $segt)
842 if(($segt[
'action'] ??
'') != self::ACTION_DEL && !$flagDeleteContent)
844 if($segt[
'segType'] ==
'text')
846 $filteredHTML .= $segt[
'value'];
848 elseif($segt[
'segType'] ==
'tag')
850 if($segt[
'tagType'] ==
'open')
852 $filteredHTML .=
'<'.$segt[
'tagName'];
854 if(isset($segt[
'attr']) && is_array($segt[
'attr']))
855 foreach($segt[
'attr'] as $attr_key => $attr_val)
856 $filteredHTML .=
' '.$attr_key.
'="'.$attr_val.
'"';
858 if (
count($this->arHtmlTags[$segt[
'tagName']]) && ($this->arHtmlTags[$segt[
'tagName']][
count($this->arHtmlTags[$segt[
'tagName']])-1] ==
false))
859 $filteredHTML .=
" /";
861 $filteredHTML .=
'>';
863 elseif($segt[
'tagType'] ==
'close')
864 $filteredHTML .=
'</'.$segt[
'tagName'].
'>';
869 if(isset($segt[
'tagName']) && in_array($segt[
'tagName'], $this->delTagsWithContent))
871 $flagDeleteContent = $segt[
'tagType'] ==
'open';
876 if(!$this->bHtmlSpecChars && $html != $filteredHTML)
881 return $filteredHTML;
903 foreach($arTagAttrs as $arTagAttr)
906 $arTagAttr[1] = mb_strtolower($arTagAttr[1]);
907 $attrAllowed = in_array($arTagAttr[1], $this->arHtmlTags[$currTag],
true);
909 if (!$attrAllowed && array_key_exists($arTagAttr[1], $this->additionalAttrs))
911 $attrAllowed =
true === call_user_func($this->additionalAttrs[$arTagAttr[1]][
'tag'], $currTag);
917 $arTagAttr[3] = str_replace(
'"',
"'", $arTagAttr[3]);
935 protected function CleanTable(&$seg, &$openTagsStack, $segIndex, $delTextBetweenTags=
true)
940 $arOpenClose =
array();
942 for ($tElCategory=self::TABLE_COLS;$tElCategory>self::TABLE_TOP;$tElCategory--)
944 if($this->arTableTags[$seg[$segIndex][
'tagName']] != $tElCategory)
948 for($j=$segIndex-1;$j>=0;$j--)
950 if ($seg[$j][
'segType'] !=
'tag' || !array_key_exists($seg[$j][
'tagName'], $this->arTableTags))
953 if(isset($seg[$j][
'action']) && $seg[$j][
'action'] == self::ACTION_DEL)
956 if($tElCategory == self::TABLE_COLS)
958 if($this->arTableTags[$seg[$j][
'tagName']] == self::TABLE_COLS || $this->arTableTags[$seg[$j][
'tagName']] == self::TABLE_ROWS)
962 if($this->arTableTags[$seg[$j][
'tagName']] <= $tElCategory)
969 if (!isset($arOpenClose[$seg[$j][
'tagName']][$seg[$j][
'tagType']]))
971 $arOpenClose[$seg[$j][
'tagName']][$seg[$j][
'tagType']] = 0;
973 $arOpenClose[$seg[$j][
'tagName']][$seg[$j][
'tagType']]++;
976 $openCount = $arOpenClose[$seg[$j][
'tagName']][
'open'] ?? 0;
977 $closeCount = $arOpenClose[$seg[$j][
'tagName']][
'close'] ?? 0;
978 if($openCount <= $closeCount)
985 if(!$delTextBetweenTags)
989 for(
$k=$segIndex-1;
$k>$j;
$k--)
992 if($seg[
$k][
'segType'] ==
'text' && !preg_match(
"#[^\n\r\s]#iu", $seg[
$k][
'value']))
995 $seg[
$k][
'action'] = self::ACTION_DEL;
996 if(isset($seg[
$k][
'closeIndex']))
997 unset($openTagsStack[$seg[
$k][
'closeIndex']]);
1005 $seg[$segIndex][
'action'] = self::ACTION_DEL;