1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
sanitizer.php
См. документацию.
1<?
2 IncludeModuleLangFile(__FILE__);
23 {
31
32 const TABLE_TOP = 0;
33 const TABLE_CAPT = 1;
34 const TABLE_GROUP = 2;
35 const TABLE_ROWS = 3;
36 const TABLE_COLS = 4;
37
38 const ACTION_DEL = 'del';
39 const ACTION_ADD = 'add';
40 const ACTION_DEL_WITH_CONTENT = 'del_with_content';
41
46 protected static $arOldTags = array();
47
48 protected $arHtmlTags = array();
49 protected $bHtmlSpecChars = true;
50 protected $bDelSanitizedTags = true;
51 protected $bDoubleEncode = true;
52 protected $secLevel = self::SECURE_LEVEL_HIGH;
53 protected $additionalAttrs = array();
54 protected $arNoClose = array(
55 'br','hr','img','area','base',
56 'basefont','col','frame','input',
57 'isindex','link','meta','param'
58 );
59 protected $localAlph;
60
61 protected $arTableTags = array(
62 'table' => self::TABLE_TOP,
63 'caption' => self::TABLE_CAPT,
64 'thead' => self::TABLE_GROUP,
65 'tfoot' => self::TABLE_GROUP,
66 'tbody' => self::TABLE_GROUP,
67 'tr' => self::TABLE_ROWS,
68 'th' => self::TABLE_COLS,
69 'td' => self::TABLE_COLS
70 );
71
76 protected $delTagsWithContent = ['script', 'style'];
77
81 public function __construct()
82 {
83 if(SITE_CHARSET == "UTF-8")
84 {
85 $this->localAlph="\p{L}".GetMessage("SNT_SYMB_NONE_LETTERS");
86 }
87 elseif(LANGUAGE_ID != "en")
88 {
89 $this->localAlph=GetMessage("SNT_SYMB");
90 }
91 else
92 {
93 $this->localAlph="";
94 }
95
96 $this->localAlph .= '\\x80-\\xFF';
97 }
98
117 public function allowAttributes(array $attrs)
118 {
119 foreach ($attrs as $code => $item)
120 {
121 if (
122 isset($item['tag']) && is_callable($item['tag']) &&
123 isset($item['content']) && is_callable($item['content'])
124 )
125 {
126 $this->additionalAttrs[$code] = $item;
127 }
128 }
129 }
130
136 public function AddTags($arTags)
137 {
138 if(!is_array($arTags))
139 return false;
140
141 $counter = 0;
142 $this->secLevel = self::SECURE_LEVEL_CUSTOM;
143
144 foreach($arTags as $tagName => $arAttrs)
145 {
146 $tagName = mb_strtolower($tagName);
147 $arAttrs = array_change_key_case($arAttrs, CASE_LOWER);
148 $this->arHtmlTags[$tagName] = $arAttrs;
149 $counter++;
150 }
151
152 return $counter;
153 }
154
158 public function UpdateTags($arTags)
159 {
160 return $this->AddTags($arTags);
161 }
162
168 public function DelTags($arTagNames)
169 {
170 if(!is_array($arTagNames))
171 return false;
172
173 $this->secLevel = self::SECURE_LEVEL_CUSTOM;
174 $arTmp = array();
175 $counter = 0;
176
177 foreach ($this->arHtmlTags as $tagName => $arAttrs)
178 foreach ($arTagNames as $delTagName)
179 if(mb_strtolower($delTagName) != $tagName)
180 $arTmp[$tagName] = $arAttrs;
181 else
182 $counter++;
183
184 $this->arHtmlTags = $arTmp;
185 return $counter;
186 }
187
191 public function DeleteAttributes(array $arDeleteAttrs)
192 {
193 $this->secLevel = self::SECURE_LEVEL_CUSTOM;
194 $arResultTags = array();
195 foreach ($this->arHtmlTags as $tagName => $arAttrs)
196 {
197 $arResultTags[$tagName] = array_diff($arAttrs, $arDeleteAttrs);
198 }
199 $this->arHtmlTags = $arResultTags;
200 }
201
205 public function DelAllTags()
206 {
207 $this->secLevel = self::SECURE_LEVEL_CUSTOM;
208 $this->arHtmlTags = array();
209 }
210
218 public function ApplyDoubleEncode($bApply=true)
219 {
220 if($bApply)
221 $this->bDoubleEncode = true;
222 else
223 $this->bDoubleEncode = false;
224 }
225
233 public function ApplyHtmlSpecChars($bApply=true)
234 {
235 if($bApply)
236 {
237 $this->bHtmlSpecChars = true;
238 }
239 else
240 {
241 $this->bHtmlSpecChars = false;
242 trigger_error('It is strongly not recommended to use \CBXSanitizer::ApplyHtmlSpecChars(false)', E_USER_WARNING);
243 }
244 }
245
252 public function DeleteSanitizedTags($bApply=true)
253 {
254 if($bApply)
255 $this->bDelSanitizedTags = true;
256 else
257 $this->bDelSanitizedTags = false;
258 }
259
266 public function SetLevel($secLevel)
267 {
268 if($secLevel!=self::SECURE_LEVEL_HIGH && $secLevel!=self::SECURE_LEVEL_MIDDLE && $secLevel!=self::SECURE_LEVEL_LOW)
269 $secLevel=self::SECURE_LEVEL_HIGH;
270
271 switch ($secLevel)
272 {
273 case self::SECURE_LEVEL_HIGH:
274 $arTags = array(
275 'b' => array(),
276 'br' => array(),
277 'big' => array(),
278 'blockquote' => array(),
279 'code' => array(),
280 'del' => array(),
281 'dt' => array(),
282 'dd' => array(),
283 'font' => array(),
284 'h1' => array(),
285 'h2' => array(),
286 'h3' => array(),
287 'h4' => array(),
288 'h5' => array(),
289 'h6' => array(),
290 'hr' => array(),
291 'i' => array(),
292 'ins' => array(),
293 'li' => array(),
294 'ol' => array(),
295 'p' => array(),
296 'small' => array(),
297 's' => array(),
298 'sub' => array(),
299 'sup' => array(),
300 'strong' => array(),
301 'pre' => array(),
302 'u' => array(),
303 'ul' => array()
304 );
305
306 break;
307
308 case self::SECURE_LEVEL_MIDDLE:
309 $arTags = array(
310 'a' => array('href', 'title','name','alt'),
311 'b' => array(),
312 'br' => array(),
313 'big' => array(),
314 'blockquote' => array('title'),
315 'code' => array(),
316 'caption' => array(),
317 'del' => array('title'),
318 'dt' => array(),
319 'dd' => array(),
320 'font' => array('color','size'),
321 'color' => array(),
322 'h1' => array(),
323 'h2' => array(),
324 'h3' => array(),
325 'h4' => array(),
326 'h5' => array(),
327 'h6' => array(),
328 'hr' => array(),
329 'i' => array(),
330 'img' => array('src','alt','height','width','title'),
331 'ins' => array('title'),
332 'li' => array(),
333 'ol' => array(),
334 'p' => array(),
335 'pre' => array(),
336 's' => array(),
337 'small' => array(),
338 'strong' => array(),
339 'sub' => array(),
340 'sup' => array(),
341 'table' => array('border','width'),
342 'tbody' => array('align','valign'),
343 'td' => array('width','height','align','valign'),
344 'tfoot' => array('align','valign'),
345 'th' => array('width','height'),
346 'thead' => array('align','valign'),
347 'tr' => array('align','valign'),
348 'u' => array(),
349 'ul' => array()
350 );
351 break;
352
353 case self::SECURE_LEVEL_LOW:
354 $arTags = array(
355 'a' => array('href', 'title','name','style','id','class','shape','coords','alt','target'),
356 'b' => array('style','id','class'),
357 'br' => array('style','id','class'),
358 'big' => array('style','id','class'),
359 'blockquote' => array('title','style','id','class'),
360 'caption' => array('style','id','class'),
361 'code' => array('style','id','class'),
362 'del' => array('title','style','id','class'),
363 'div' => array('title','style','id','class','align'),
364 'dt' => array('style','id','class'),
365 'dd' => array('style','id','class'),
366 'font' => array('color','size','face','style','id','class'),
367 'h1' => array('style','id','class','align'),
368 'h2' => array('style','id','class','align'),
369 'h3' => array('style','id','class','align'),
370 'h4' => array('style','id','class','align'),
371 'h5' => array('style','id','class','align'),
372 'h6' => array('style','id','class','align'),
373 'hr' => array('style','id','class'),
374 'i' => array('style','id','class'),
375 'img' => array('style','id','class','src','alt','height','width','title','align'),
376 'ins' => array('title','style','id','class'),
377 'li' => array('style','id','class'),
378 'map' => array('shape','coords','href','alt','title','style','id','class','name'),
379 'ol' => array('style','id','class'),
380 'p' => array('style','id','class','align'),
381 'pre' => array('style','id','class'),
382 's' => array('style','id','class'),
383 'small' => array('style','id','class'),
384 'strong' => array('style','id','class'),
385 'span' => array('title','style','id','class','align'),
386 'sub' => array('style','id','class'),
387 'sup' => array('style','id','class'),
388 'table' => array('border','width','style','id','class','cellspacing','cellpadding'),
389 'tbody' => array('align','valign','style','id','class'),
390 'td' => array('width','height','style','id','class','align','valign','colspan','rowspan'),
391 'tfoot' => array('align','valign','style','id','class','align','valign'),
392 'th' => array('width','height','style','id','class','colspan','rowspan'),
393 'thead' => array('align','valign','style','id','class'),
394 'tr' => array('align','valign','style','id','class'),
395 'u' => array('style','id','class'),
396 'ul' => array('style','id','class')
397 );
398 break;
399 default:
400 $arTags = array();
401 break;
402 }
403
404 $this->DelAllTags();
405 $this->AddTags($arTags);
406 $this->secLevel = $secLevel;
407 }
408
409 // Checks if tag's attributes are in white list ($this->arHtmlTags)
410 protected function IsValidAttr(&$arAttr)
411 {
412 if (!isset($arAttr[1]) || !isset($arAttr[3]))
413 {
414 return false;
415 }
416
417 $attr = mb_strtolower($arAttr[1]);
418 $attrValue = $this->Decode($arAttr[3]);
419
420 switch ($attr)
421 {
422 case 'src':
423 case 'href':
424 case 'data-url':
425 if(!preg_match("#^(http://|https://|ftp://|file://|mailto:|callto:|skype:|tel:|sms:|\\#|/)#iu", $attrValue))
426 {
427 $arAttr[3] = 'http://' . $arAttr[3];
428 }
429 $valid = (!preg_match("#javascript:|data:|[^\\w".$this->localAlph."a-zA-Z:/\\.=@;,!~\\*\\&\\#\\)(%\\s\\+\$\\?\\-\\[\\]]#iu", $attrValue))
430 ? true : false;
431 break;
432
433 case 'height':
434 case 'width':
435 case 'cellpadding':
436 case 'cellspacing':
437 $valid = !preg_match("#^[^0-9\\-]+(px|%|\\*)*#iu", $attrValue)
438 ? true : false;
439 break;
440
441 case 'title':
442 case 'alt':
443 $valid = !preg_match("#[^\\w".$this->localAlph."\\.\\?!,:;\\s\\-]#iu", $attrValue)
444 ? true : false;
445 break;
446
447 case 'style':
448 $attrValue = str_replace('&quot;', '', $attrValue);
449 $valid = !preg_match("#(behavior|expression|javascript)#iu", $attrValue) && !preg_match("#[^\\/\\w\\s)(!%,:\\.;\\-\\#\\']#iu", $attrValue)
450 ? true : false;
451 break;
452
453 case 'coords':
454 $valid = !preg_match("#[^0-9\\s,\\-]#iu", $attrValue)
455 ? true : false;
456 break;
457
458 default:
459 if (array_key_exists($attr, $this->additionalAttrs))
460 {
461 $valid = true === call_user_func_array(
462 $this->additionalAttrs[$attr]['content'],
463 array($attrValue)
464 );
465 }
466 else
467 {
468 $valid = !preg_match("#[^\\s\\w" . $this->localAlph . "\\-\\#\\.\/;]#iu", $attrValue)
469 ? true : false;
470 }
471 break;
472 }
473
474 return $valid;
475 }
476
477 protected function encodeAttributeValue(array $attr)
478 {
479 if (!$this->bHtmlSpecChars)
480 {
481 return $attr[3];
482 }
483
484 $result = $attr[3];
485 $flags = ENT_QUOTES;
486
487 if ($attr[1] === 'style')
488 {
489 $flags = ENT_COMPAT;
490 }
491 elseif ($attr[1] === 'href')
492 {
493 $result = str_replace('&', '##AMP##', $result);
494 }
495
496 $result = htmlspecialchars($result, $flags, LANG_CHARSET, $this->bDoubleEncode);
497
498 if ($attr[1] === 'href')
499 {
500 $result = str_replace('##AMP##', '&', $result);
501 }
502
503 return $result;
504 }
505
510 public function GetTags()
511 {
512 if(!is_array($this->arHtmlTags))
513 return false;
514
515 $confStr="";
516
517 foreach ($this->arHtmlTags as $tag => $arAttrs)
518 {
519 $confStr.=$tag." (";
520 foreach ($arAttrs as $attr)
521 if($attr)
522 $confStr.=" ".$attr." ";
523 $confStr.=")<br>";
524 }
525
526 return $confStr;
527 }
528
532 public static function SetTags($arTags)
533 {
534 self::$arOldTags = $arTags;
535
536 /* for next version
537 $this->DelAllTags();
538
539 return $this->AddTags($arTags);
540 */
541 }
542
546 public static function Sanitize($html, $secLevel='HIGH', $htmlspecialchars=true, $delTags=true)
547 {
548 $Sanitizer = new self;
549
550 if(empty(self::$arOldTags))
551 $Sanitizer->SetLevel(self::SECURE_LEVEL_HIGH);
552 else
553 {
554 $Sanitizer->DelAllTags();
555 $Sanitizer->AddTags(self::$arOldTags);
556 }
557
558 $Sanitizer->ApplyHtmlSpecChars($htmlspecialchars);
559 $Sanitizer->DeleteSanitizedTags($delTags);
560 $Sanitizer->ApplyDoubleEncode();
561
562 return $Sanitizer->SanitizeHtml($html);
563 }
564
570 protected function splitHtml($html)
571 {
572 $result = [];
573 $arData = preg_split('/(<[^<>]+>)/siu', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
574
575 foreach($arData as $i => $chunk)
576 {
577 $isTag = $i % 2 || (str_starts_with($chunk, '<') && str_ends_with($chunk, '>'));
578
579 if ($isTag)
580 {
581 $result[] = array('segType'=>'tag', 'value'=>$chunk);
582 }
583 elseif ($chunk != "")
584 {
585 $result[]=array('segType'=>'text', 'value'=> $chunk);
586 }
587 }
588
589 return $result;
590 }
591
598 public function SanitizeHtml($html)
599 {
600 if(empty($this->arHtmlTags))
601 $this->SetLevel(self::SECURE_LEVEL_HIGH);
602
603 $openTagsStack = array();
604 $isCode = false;
605
606 $seg = $this->splitHtml($html);
607
608 //process segments
609 $segCount = count($seg);
610 for($i=0; $i<$segCount; $i++)
611 {
612 if($seg[$i]['segType'] == 'text')
613 {
614 if (trim($seg[$i]['value']) && ($tp = array_search('table', $openTagsStack)) !== false)
615 {
616 $cellTags = array_intersect(array('td', 'th'), array_keys($this->arHtmlTags));
617 if ($cellTags && !array_intersect($cellTags, array_slice($openTagsStack, $tp+1)))
618 {
619 array_splice($seg, $i, 0, array(array('segType' => 'tag', 'value' => sprintf('<%s>', reset($cellTags)))));
620 $i--; $segCount++;
621
622 continue;
623 }
624 }
625
626 if ($this->bHtmlSpecChars)
627 {
628 $openTagsStackSize = count($openTagsStack);
629 $entQuotes = ($openTagsStackSize && $openTagsStack[$openTagsStackSize-1] === 'style' ? ENT_NOQUOTES : ENT_QUOTES);
630
631 $seg[$i]['value'] = htmlspecialchars(
632 $seg[$i]['value'],
633 $entQuotes,
635 $this->bDoubleEncode
636 );
637 }
638 }
639 elseif(
640 $seg[$i]['segType'] == 'tag'
641 && (
642 preg_match('/^<!--\\[if\\s+((?:mso|gt|lt|gte|lte|\\||!|[0-9]+|\\(|\\))\\s*)+\\]>$/', $seg[$i]['value'])
643 || preg_match('/^<!\\[endif\\]-->$/', $seg[$i]['value'])
644 )
645 )
646 {
647 //Keep ms html comments https://stackoverflow.design/email/base/mso/
648 $seg[$i]['segType'] = 'text';
649 }
650 elseif($seg[$i]['segType'] == 'tag')
651 {
652 //find tag type (open/close), tag name, attributies
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] ?? '');
656
657 if(($seg[$i]['tagName']=='code') && ($seg[$i]['tagType']=='close'))
658 $isCode = false;
659
660 //if tag founded inside <code></code> it is simple text
661 if($isCode)
662 {
663 $seg[$i]['segType'] = 'text';
664 $i--;
665 continue;
666 }
667
668 if($seg[$i]['tagType'] == 'open')
669 {
670 // if tag unallowed screen it, or erase
671 if(!array_key_exists($seg[$i]['tagName'], $this->arHtmlTags))
672 {
673 if($this->bDelSanitizedTags)
674 {
675 $seg[$i]['action'] = self::ACTION_DEL;
676 }
677 else
678 {
679 $seg[$i]['segType'] = 'text';
680 $i--;
681 continue;
682 }
683 }
684 //if allowed
685 else
686 {
687 if (in_array('table', $openTagsStack))
688 {
689 if ($openTagsStack[count($openTagsStack)-1] == 'table')
690 {
691 if (array_key_exists('tr', $this->arHtmlTags) && !in_array($seg[$i]['tagName'], array('thead', 'tfoot', 'tbody', 'tr')))
692 {
693 array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'open', 'tagName' => 'tr', 'action' => self::ACTION_ADD)));
694 $i++; $segCount++;
695
696 $openTagsStack[] = 'tr';
697 }
698 }
699
700 if (in_array($openTagsStack[count($openTagsStack)-1], array('thead', 'tfoot', 'tbody')))
701 {
702 if (array_key_exists('tr', $this->arHtmlTags) && $seg[$i]['tagName'] != 'tr')
703 {
704 array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'open', 'tagName' => 'tr', 'action' => self::ACTION_ADD)));
705 $i++; $segCount++;
706
707 $openTagsStack[] = 'tr';
708 }
709 }
710
711 if ($seg[$i]['tagName'] == 'tr')
712 {
713 for ($j = count($openTagsStack)-1; $j >= 0; $j--)
714 {
715 if (in_array($openTagsStack[$j], array('table', 'thead', 'tfoot', 'tbody')))
716 break;
717
718 array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'close', 'tagName' => $openTagsStack[$j], 'action' => self::ACTION_ADD)));
719 $i++; $segCount++;
720
721 array_splice($openTagsStack, $j, 1);
722 }
723 }
724
725 if ($openTagsStack[count($openTagsStack)-1] == 'tr')
726 {
727 $cellTags = array_intersect(array('td', 'th'), array_keys($this->arHtmlTags));
728 if ($cellTags && !in_array($seg[$i]['tagName'], $cellTags))
729 {
730 array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'open', 'tagName' => reset($cellTags), 'action' => self::ACTION_ADD)));
731 $i++; $segCount++;
732
733 $openTagsStack[] = 'td';
734 }
735 }
736
737 if (in_array($seg[$i]['tagName'], array('td', 'th')))
738 {
739 for ($j = count($openTagsStack)-1; $j >= 0; $j--)
740 {
741 if ($openTagsStack[$j] == 'tr')
742 break;
743
744 array_splice($seg, $i, 0, array(array('segType' => 'tag', 'tagType' => 'close', 'tagName' => $openTagsStack[$j], 'action' => self::ACTION_ADD)));
745 $i++; $segCount++;
746
747 array_splice($openTagsStack, $j, 1);
748 }
749 }
750 }
751
752 //Processing valid tables
753 //if find 'tr','td', etc...
754 if(array_key_exists($seg[$i]['tagName'], $this->arTableTags))
755 {
756 $this->CleanTable($seg, $openTagsStack, $i, false);
757
758 if(isset($seg[$i]['action']) && $seg[$i]['action'] == self::ACTION_DEL)
759 continue;
760 }
761
762 $seg[$i]['attr'] = $this->processAttributes(
763 (string)$matches[3], //attributes string
764 (string)$seg[$i]['tagName']
765 );
766
767 if($seg[$i]['tagName'] === 'code')
768 {
769 $isCode = true;
770 }
771
772 //if tag need close tag add it to stack opened tags
773 if(!in_array($seg[$i]['tagName'], $this->arNoClose))
774 {
775 $openTagsStack[] = $seg[$i]['tagName'];
776 $seg[$i]['closeIndex'] = count($openTagsStack)-1;
777 }
778 }
779 }
780 //if closing tag
781 else
782 { //if tag allowed
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)))
784 {
785 if($seg[$i]['tagName'] == 'code')
786 {
787 $isCode = false;
788 }
789 //if open tags stack is empty, or not include it's name lets screen/erase it
790 if((empty($openTagsStack)) || (!in_array($seg[$i]['tagName'], $openTagsStack)))
791 {
792 if($this->bDelSanitizedTags || $this->arNoClose)
793 {
794 $seg[$i]['action'] = self::ACTION_DEL;
795 }
796 else
797 {
798 $seg[$i]['segType'] = 'text';
799 $i--;
800 continue;
801 }
802 }
803 else
804 {
805 //if this tag don't match last from open tags stack , adding right close tag
806 $tagName = array_pop($openTagsStack);
807 if($seg[$i]['tagName'] != $tagName)
808 {
809 array_splice($seg, $i, 0, array(array('segType'=>'tag', 'tagType'=>'close', 'tagName'=>$tagName, 'action'=>self::ACTION_ADD)));
810 $segCount++;
811 }
812 }
813 }
814 //if tag unallowed erase it
815 else
816 {
817 if($this->bDelSanitizedTags)
818 {
819 $seg[$i]['action'] = self::ACTION_DEL;
820 }
821 else
822 {
823 $seg[$i]['segType'] = 'text';
824 $i--;
825 continue;
826 }
827 }
828 }
829 }
830 }
831
832 //close tags stayed in stack
833 foreach(array_reverse($openTagsStack) as $val)
834 array_push($seg, array('segType'=>'tag', 'tagType'=>'close', 'tagName'=>$val, 'action'=>self::ACTION_ADD));
835
836 //build filtered code and return it
837 $filteredHTML = '';
838 $flagDeleteContent = false;
839
840 foreach($seg as $segt)
841 {
842 if(($segt['action'] ?? '') != self::ACTION_DEL && !$flagDeleteContent)
843 {
844 if($segt['segType'] == 'text')
845 {
846 $filteredHTML .= $segt['value'];
847 }
848 elseif($segt['segType'] == 'tag')
849 {
850 if($segt['tagType'] == 'open')
851 {
852 $filteredHTML .= '<'.$segt['tagName'];
853
854 if(isset($segt['attr']) && is_array($segt['attr']))
855 foreach($segt['attr'] as $attr_key => $attr_val)
856 $filteredHTML .= ' '.$attr_key.'="'.$attr_val.'"';
857
858 if (count($this->arHtmlTags[$segt['tagName']]) && ($this->arHtmlTags[$segt['tagName']][count($this->arHtmlTags[$segt['tagName']])-1] == false))
859 $filteredHTML .= " /";
860
861 $filteredHTML .= '>';
862 }
863 elseif($segt['tagType'] == 'close')
864 $filteredHTML .= '</'.$segt['tagName'].'>';
865 }
866 }
867 else
868 {
869 if(isset($segt['tagName']) && in_array($segt['tagName'], $this->delTagsWithContent))
870 {
871 $flagDeleteContent = $segt['tagType'] == 'open';
872 }
873 }
874 }
875
876 if(!$this->bHtmlSpecChars && $html != $filteredHTML)
877 {
878 $filteredHTML = $this->SanitizeHtml($filteredHTML);
879 }
880
881 return $filteredHTML;
882 }
883
884 protected function extractAttributes(string $attrData): array
885 {
886 $result = [];
887
888 preg_match_all(
889 '#([a-z0-9_-]+)\s*=\s*([\'\"]?)(?:\s*)(.*?)(?:\s*)\2(\s|$|(?:\/\s*$))+#isu',
890 $attrData,
891 $result,
892 PREG_SET_ORDER
893 );
894
895 return $result;
896 }
897
898 protected function processAttributes(string $attrData, string $currTag): array
899 {
900 $attr = [];
901 $arTagAttrs = $this->extractAttributes($attrData);
902
903 foreach($arTagAttrs as $arTagAttr)
904 {
905 // Attribute name
906 $arTagAttr[1] = mb_strtolower($arTagAttr[1]);
907 $attrAllowed = in_array($arTagAttr[1], $this->arHtmlTags[$currTag], true);
908
909 if (!$attrAllowed && array_key_exists($arTagAttr[1], $this->additionalAttrs))
910 {
911 $attrAllowed = true === call_user_func($this->additionalAttrs[$arTagAttr[1]]['tag'], $currTag);
912 }
913
914 if ($attrAllowed)
915 {
916 // Attribute value. Wrap attribute by "
917 $arTagAttr[3] = str_replace('"', "'", $arTagAttr[3]);
918
919 if($this->IsValidAttr($arTagAttr))
920 {
921 $attr[$arTagAttr[1]] = $this->encodeAttributeValue($arTagAttr);
922 }
923 }
924 }
925
926 return $attr;
927 }
928
935 protected function CleanTable(&$seg, &$openTagsStack, $segIndex, $delTextBetweenTags=true)
936 {
937 //if we found up level or not
938 $bFindUp = false;
939 //count open & close tags
940 $arOpenClose = array();
941
942 for ($tElCategory=self::TABLE_COLS;$tElCategory>self::TABLE_TOP;$tElCategory--)
943 {
944 if($this->arTableTags[$seg[$segIndex]['tagName']] != $tElCategory)
945 continue;
946
947 //find back upper level
948 for($j=$segIndex-1;$j>=0;$j--)
949 {
950 if ($seg[$j]['segType'] != 'tag' || !array_key_exists($seg[$j]['tagName'], $this->arTableTags))
951 continue;
952
953 if(isset($seg[$j]['action']) && $seg[$j]['action'] == self::ACTION_DEL)
954 continue;
955
956 if($tElCategory == self::TABLE_COLS)
957 {
958 if($this->arTableTags[$seg[$j]['tagName']] == self::TABLE_COLS || $this->arTableTags[$seg[$j]['tagName']] == self::TABLE_ROWS)
959 $bFindUp = true;
960 }
961 else
962 if($this->arTableTags[$seg[$j]['tagName']] <= $tElCategory)
963 $bFindUp = true;
964
965 if(!$bFindUp)
966 continue;
967
968 //count opened and closed tags
969 if (!isset($arOpenClose[$seg[$j]['tagName']][$seg[$j]['tagType']]))
970 {
971 $arOpenClose[$seg[$j]['tagName']][$seg[$j]['tagType']] = 0;
972 }
973 $arOpenClose[$seg[$j]['tagName']][$seg[$j]['tagType']]++;
974
975 //if opened tag not found yet, searching for more
976 $openCount = $arOpenClose[$seg[$j]['tagName']]['open'] ?? 0;
977 $closeCount = $arOpenClose[$seg[$j]['tagName']]['close'] ?? 0;
978 if($openCount <= $closeCount)
979 {
980 $bFindUp = false;
981 continue;
982 }
983
984
985 if(!$delTextBetweenTags)
986 break;
987
988 //if find up level let's mark all middle text and tags for del-action
989 for($k=$segIndex-1;$k>$j;$k--)
990 {
991 //lt's save text-format
992 if($seg[$k]['segType'] == 'text' && !preg_match("#[^\n\r\s]#iu", $seg[$k]['value']))
993 continue;
994
995 $seg[$k]['action'] = self::ACTION_DEL;
996 if(isset($seg[$k]['closeIndex']))
997 unset($openTagsStack[$seg[$k]['closeIndex']]);
998 }
999
1000 break;
1001
1002 }
1003 //if we didn't find up levels,lets mark this block as del
1004 if(!$bFindUp)
1005 $seg[$segIndex]['action'] = self::ACTION_DEL;
1006
1007 break;
1008
1009 }
1010 return $bFindUp;
1011 }
1012
1018 public function Decode($str)
1019 {
1020 $str1="";
1021
1022 while($str1 <> $str)
1023 {
1024 $str1 = $str;
1025 $str = $this->_decode($str);
1026 $str = str_replace("\x00", "", $str);
1027 $str = preg_replace("/\&\#0+(;|([^\d;]))/is", "\\2", $str);
1028 $str = preg_replace("/\&\#x0+(;|([^\da-f;]))/is", "\\2", $str);
1029 }
1030
1031 return $str1;
1032 }
1033
1034 /*
1035 Function is used in regular expressions in order to decode characters presented as &#123;
1036 */
1037 protected function _decode_cb($in)
1038 {
1039 $ad = $in[2];
1040 if($ad == ';')
1041 $ad="";
1042 $num = intval($in[1]);
1043 return chr($num).$ad;
1044 }
1045
1046 /*
1047 Function is used in regular expressions in order to decode characters presented as &#xAB;
1048 */
1049 protected function _decode_cb_hex($in)
1050 {
1051 $ad = $in[2];
1052 if($ad==';')
1053 $ad="";
1054 $num = intval(hexdec($in[1]));
1055 return chr($num).$ad;
1056 }
1057
1058 /*
1059 Decodes string from html codes &#***;
1060 One pass!
1061 -- Decode only a-zA-Z:().=, because only theese are used in filters
1062 */
1063 protected function _decode($str)
1064 {
1065 $str = preg_replace_callback("/\&\#(\d+)([^\d])/is", array("CBXSanitizer", "_decode_cb"), $str);
1066 $str = preg_replace_callback("/\&\#x([\da-f]+)([^\da-f])/is", array("CBXSanitizer", "_decode_cb_hex"), $str);
1067 return str_replace(array("&colon;","&tab;","&newline;"), array(":","\t","\n"), $str);
1068 }
1069
1073 public function setDelTagsWithContent(array $tags)
1074 {
1075 $this->delTagsWithContent = $tags;
1076 }
1077
1081 public function getDelTagsWithContent()
1082 {
1084 }
1085 };
Определения sanitizer.php:23
$bHtmlSpecChars
Определения sanitizer.php:49
const TABLE_ROWS
Определения sanitizer.php:35
__construct()
Определения sanitizer.php:81
splitHtml($html)
Определения sanitizer.php:570
DeleteSanitizedTags($bApply=true)
Определения sanitizer.php:252
_decode_cb_hex($in)
Определения sanitizer.php:1049
Decode($str)
Определения sanitizer.php:1018
const TABLE_COLS
Определения sanitizer.php:36
encodeAttributeValue(array $attr)
Определения sanitizer.php:477
UpdateTags($arTags)
Определения sanitizer.php:158
DelAllTags()
Определения sanitizer.php:205
$bDoubleEncode
Определения sanitizer.php:51
SetLevel($secLevel)
Определения sanitizer.php:266
$arNoClose
Определения sanitizer.php:54
SanitizeHtml($html)
Определения sanitizer.php:598
static $arOldTags
Определения sanitizer.php:46
extractAttributes(string $attrData)
Определения sanitizer.php:884
const SECURE_LEVEL_MIDDLE
Определения sanitizer.php:29
$bDelSanitizedTags
Определения sanitizer.php:50
DeleteAttributes(array $arDeleteAttrs)
Определения sanitizer.php:191
_decode($str)
Определения sanitizer.php:1063
processAttributes(string $attrData, string $currTag)
Определения sanitizer.php:898
const SECURE_LEVEL_LOW
Определения sanitizer.php:30
ApplyHtmlSpecChars($bApply=true)
Определения sanitizer.php:233
$arHtmlTags
Определения sanitizer.php:48
const TABLE_TOP
Определения sanitizer.php:32
const ACTION_DEL
Определения sanitizer.php:38
const SECURE_LEVEL_HIGH
Определения sanitizer.php:28
CleanTable(&$seg, &$openTagsStack, $segIndex, $delTextBetweenTags=true)
Определения sanitizer.php:935
IsValidAttr(&$arAttr)
Определения sanitizer.php:410
AddTags($arTags)
Определения sanitizer.php:136
$delTagsWithContent
Определения sanitizer.php:76
const SECURE_LEVEL_CUSTOM
Определения sanitizer.php:27
_decode_cb($in)
Определения sanitizer.php:1037
const ACTION_ADD
Определения sanitizer.php:39
setDelTagsWithContent(array $tags)
Определения sanitizer.php:1073
$secLevel
Определения sanitizer.php:52
const TABLE_GROUP
Определения sanitizer.php:34
DelTags($arTagNames)
Определения sanitizer.php:168
const TABLE_CAPT
Определения sanitizer.php:33
GetTags()
Определения sanitizer.php:510
ApplyDoubleEncode($bApply=true)
Определения sanitizer.php:218
$additionalAttrs
Определения sanitizer.php:53
$arTableTags
Определения sanitizer.php:61
$localAlph
Определения sanitizer.php:59
getDelTagsWithContent()
Определения sanitizer.php:1081
allowAttributes(array $attrs)
Определения sanitizer.php:117
static SetTags($arTags)
Определения sanitizer.php:532
static Sanitize($html, $secLevel='HIGH', $htmlspecialchars=true, $delTags=true)
Определения sanitizer.php:546
const ACTION_DEL_WITH_CONTENT
Определения sanitizer.php:40
$str
Определения commerceml2.php:63
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
const SITE_CHARSET
Определения include.php:62
const LANG_CHARSET
Определения include.php:65
IncludeModuleLangFile($filepath, $lang=false, $bReturnArray=false)
Определения tools.php:3778
GetMessage($name, $aReplace=null)
Определения tools.php:3397
if(intval($iTestTransaction) > 0) $arTmp
Определения payment.php:22
$counter
Определения options.php:5
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
$i
Определения factura.php:643
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$val
Определения options.php:1793
$matches
Определения index.php:22
$k
Определения template_pdf.php:567