Skip to content

Commit 552b882

Browse files
authored
3.x - Use non-multibyte filter methods in filter (#64)
* use non mb character methods * remove obsolute comment * Re-Add stringhelper for trim
1 parent b589b6a commit 552b882

File tree

2 files changed

+85
-58
lines changed

2 files changed

+85
-58
lines changed

Tests/InputFilterTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,12 @@ public function casesGeneric()
561561
['nonbreaking nonbreaking', 'multi multi'],
562562
'From generic cases',
563563
],
564+
'trim_04' => [
565+
'trim',
566+
['Saccà', 'Saccà'],
567+
['Saccà', 'Saccà'],
568+
'CMS issue 6803'
569+
],
564570
'string_01' => [
565571
'string',
566572
'123.567',
@@ -810,6 +816,18 @@ public function allowed()
810816
'strongمحمد',
811817
'From specific utf-8 multibyte cases',
812818
],
819+
'Malformed Tag with RIGHT DOUBLE QUOTATION MARK' => [
820+
'',
821+
'style="background:url()’”><img src=x onerror=alert(1) x=<a href="test">test</a>',
822+
'style="background:url()’”>img src=x onerror=alert(1) x=test',
823+
'From specific utf-8 multibyte cases',
824+
],
825+
'UTF8offset' => [
826+
'',
827+
"\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\"><img src=x onerror=alert(1)>",
828+
"\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\">",
829+
'From specific utf-8 multibyte cases',
830+
],
813831
'Unquoted Attribute Without Space' => [
814832
'',
815833
'<img height=300>',
@@ -952,6 +970,18 @@ public function allowImg()
952970
'strongمحمد',
953971
'From specific utf-8 multibyte cases',
954972
],
973+
'Malformed Tag with RIGHT DOUBLE QUOTATION MARK' => [
974+
'',
975+
'style="background:url()’”><img src=x onerror=alert(1) x=<a href="test">test</a>',
976+
'style="background:url()’”>img src=x onerror=alert(1) x=test',
977+
'From specific utf-8 multibyte cases',
978+
],
979+
'UTF8offset' => [
980+
'',
981+
"\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\"><img src=x onerror=alert(1)>",
982+
"\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\"><img />",
983+
'From specific utf-8 multibyte cases',
984+
],
955985
'Unquoted Attribute Without Space' => [
956986
'',
957987
'<img height=300>',

src/InputFilter.php

Lines changed: 55 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -319,59 +319,59 @@ protected function cleanTags($source)
319319
$attr = '';
320320

321321
// Is there a tag? If so it will certainly start with a '<'.
322-
$tagOpenStart = StringHelper::strpos($source, '<');
322+
$tagOpenStart = strpos($source, '<');
323323

324324
while ($tagOpenStart !== false) {
325325
// Get some information about the tag we are processing
326-
$preTag .= StringHelper::substr($postTag, 0, $tagOpenStart);
327-
$postTag = StringHelper::substr($postTag, $tagOpenStart);
328-
$fromTagOpen = StringHelper::substr($postTag, 1);
329-
$tagOpenEnd = StringHelper::strpos($fromTagOpen, '>');
326+
$preTag .= substr($postTag, 0, $tagOpenStart);
327+
$postTag = substr($postTag, $tagOpenStart);
328+
$fromTagOpen = substr($postTag, 1);
329+
$tagOpenEnd = strpos($fromTagOpen, '>');
330330

331331
// Check for mal-formed tag where we have a second '<' before the first '>'
332-
$nextOpenTag = (StringHelper::strlen($postTag) > $tagOpenStart) ? StringHelper::strpos($postTag, '<', $tagOpenStart + 1) : false;
332+
$nextOpenTag = (strlen($postTag) > $tagOpenStart) ? strpos($postTag, '<', $tagOpenStart + 1) : false;
333333

334334
if (($nextOpenTag !== false) && ($nextOpenTag < $tagOpenEnd)) {
335335
// At this point we have a mal-formed tag -- remove the offending open
336-
$postTag = StringHelper::substr($postTag, 0, $tagOpenStart) . StringHelper::substr($postTag, $tagOpenStart + 1);
337-
$tagOpenStart = StringHelper::strpos($postTag, '<');
336+
$postTag = substr($postTag, 0, $tagOpenStart) . substr($postTag, $tagOpenStart + 1);
337+
$tagOpenStart = strpos($postTag, '<');
338338

339339
continue;
340340
}
341341

342342
// Let's catch any non-terminated tags and skip over them
343343
if ($tagOpenEnd === false) {
344-
$postTag = StringHelper::substr($postTag, $tagOpenStart + 1);
345-
$tagOpenStart = StringHelper::strpos($postTag, '<');
344+
$postTag = substr($postTag, $tagOpenStart + 1);
345+
$tagOpenStart = strpos($postTag, '<');
346346

347347
continue;
348348
}
349349

350350
// Do we have a nested tag?
351-
$tagOpenNested = StringHelper::strpos($fromTagOpen, '<');
351+
$tagOpenNested = strpos($fromTagOpen, '<');
352352

353353
if (($tagOpenNested !== false) && ($tagOpenNested < $tagOpenEnd)) {
354-
$preTag .= StringHelper::substr($postTag, 1, $tagOpenNested);
355-
$postTag = StringHelper::substr($postTag, ($tagOpenNested + 1));
356-
$tagOpenStart = StringHelper::strpos($postTag, '<');
354+
$preTag .= substr($postTag, 1, $tagOpenNested);
355+
$postTag = substr($postTag, ($tagOpenNested + 1));
356+
$tagOpenStart = strpos($postTag, '<');
357357

358358
continue;
359359
}
360360

361361
// Let's get some information about our tag and setup attribute pairs
362-
$tagOpenNested = (StringHelper::strpos($fromTagOpen, '<') + $tagOpenStart + 1);
363-
$currentTag = StringHelper::substr($fromTagOpen, 0, $tagOpenEnd);
364-
$tagLength = StringHelper::strlen($currentTag);
362+
$tagOpenNested = (strpos($fromTagOpen, '<') + $tagOpenStart + 1);
363+
$currentTag = substr($fromTagOpen, 0, $tagOpenEnd);
364+
$tagLength = strlen($currentTag);
365365
$tagLeft = $currentTag;
366366
$attrSet = [];
367-
$currentSpace = StringHelper::strpos($tagLeft, ' ');
367+
$currentSpace = strpos($tagLeft, ' ');
368368

369369
// Are we an open tag or a close tag?
370-
if (StringHelper::substr($currentTag, 0, 1) === '/') {
370+
if (substr($currentTag, 0, 1) === '/') {
371371
// Close Tag
372372
$isCloseTag = true;
373373
list($tagName) = explode(' ', $currentTag);
374-
$tagName = StringHelper::substr($tagName, 1);
374+
$tagName = substr($tagName, 1);
375375
} else {
376376
// Open Tag
377377
$isCloseTag = false;
@@ -388,8 +388,8 @@ protected function cleanTags($source)
388388
|| (!$tagName)
389389
|| ((\in_array(strtolower($tagName), $this->blockedTags)) && $this->xssAuto)
390390
) {
391-
$postTag = StringHelper::substr($postTag, ($tagLength + 2));
392-
$tagOpenStart = StringHelper::strpos($postTag, '<');
391+
$postTag = substr($postTag, ($tagLength + 2));
392+
$tagOpenStart = strpos($postTag, '<');
393393

394394
// Strip tag
395395
continue;
@@ -401,62 +401,61 @@ protected function cleanTags($source)
401401
*/
402402
while ($currentSpace !== false) {
403403
$attr = '';
404-
$fromSpace = StringHelper::substr($tagLeft, ($currentSpace + 1));
405-
$nextEqual = StringHelper::strpos($fromSpace, '=');
406-
$nextSpace = StringHelper::strpos($fromSpace, ' ');
407-
$openQuotes = StringHelper::strpos($fromSpace, '"');
408-
$closeQuotes = StringHelper::strpos(StringHelper::substr($fromSpace, ($openQuotes + 1)), '"') + $openQuotes + 1;
404+
$fromSpace = substr($tagLeft, ($currentSpace + 1));
405+
$nextEqual = strpos($fromSpace, '=');
406+
$nextSpace = strpos($fromSpace, ' ');
407+
$openQuotes = strpos($fromSpace, '"');
408+
$closeQuotes = strpos(substr($fromSpace, ($openQuotes + 1)), '"') + $openQuotes + 1;
409409

410410
$startAtt = '';
411411
$startAttPosition = 0;
412412

413413
// Find position of equal and open quotes ignoring
414414
if (preg_match('#\s*=\s*\"#', $fromSpace, $matches, \PREG_OFFSET_CAPTURE)) {
415-
// We have found an attribute, convert its byte position to a UTF-8 string length, using non-multibyte substr()
416415
$stringBeforeAttr = substr($fromSpace, 0, $matches[0][1]);
417-
$startAttPosition = StringHelper::strlen($stringBeforeAttr);
416+
$startAttPosition = strlen($stringBeforeAttr);
418417
$startAtt = $matches[0][0];
419-
$closeQuotePos = StringHelper::strpos(
420-
StringHelper::substr($fromSpace, ($startAttPosition + StringHelper::strlen($startAtt))),
418+
$closeQuotePos = strpos(
419+
substr($fromSpace, ($startAttPosition + strlen($startAtt))),
421420
'"'
422421
);
423-
$closeQuotes = $closeQuotePos + $startAttPosition + StringHelper::strlen($startAtt);
424-
$nextEqual = $startAttPosition + StringHelper::strpos($startAtt, '=');
425-
$openQuotes = $startAttPosition + StringHelper::strpos($startAtt, '"');
426-
$nextSpace = StringHelper::strpos(StringHelper::substr($fromSpace, $closeQuotes), ' ') + $closeQuotes;
422+
$closeQuotes = $closeQuotePos + $startAttPosition + strlen($startAtt);
423+
$nextEqual = $startAttPosition + strpos($startAtt, '=');
424+
$openQuotes = $startAttPosition + strpos($startAtt, '"');
425+
$nextSpace = strpos(substr($fromSpace, $closeQuotes), ' ') + $closeQuotes;
427426
}
428427

429428
// Do we have an attribute to process? [check for equal sign]
430429
if ($fromSpace !== '/' && (($nextEqual && $nextSpace && $nextSpace < $nextEqual) || !$nextEqual)) {
431430
if (!$nextEqual) {
432-
$attribEnd = StringHelper::strpos($fromSpace, '/') - 1;
431+
$attribEnd = strpos($fromSpace, '/') - 1;
433432
} else {
434433
$attribEnd = $nextSpace - 1;
435434
}
436435

437436
// If there is an ending, use this, if not, do not worry.
438437
if ($attribEnd > 0) {
439-
$fromSpace = StringHelper::substr($fromSpace, $attribEnd + 1);
438+
$fromSpace = substr($fromSpace, $attribEnd + 1);
440439
}
441440
}
442441

443-
if (StringHelper::strpos($fromSpace, '=') !== false) {
442+
if (strpos($fromSpace, '=') !== false) {
444443
/*
445444
* If the attribute value is wrapped in quotes we need to grab the substring from the closing quote,
446445
* otherwise grab until the next space.
447446
*/
448447
if (
449448
($openQuotes !== false)
450-
&& (StringHelper::strpos(StringHelper::substr($fromSpace, ($openQuotes + 1)), '"') !== false)
449+
&& (strpos(substr($fromSpace, ($openQuotes + 1)), '"') !== false)
451450
) {
452-
$attr = StringHelper::substr($fromSpace, 0, ($closeQuotes + 1));
451+
$attr = substr($fromSpace, 0, ($closeQuotes + 1));
453452
} else {
454-
$attr = StringHelper::substr($fromSpace, 0, $nextSpace);
453+
$attr = substr($fromSpace, 0, $nextSpace);
455454
}
456455
} else {
457456
// No more equal signs so add any extra text in the tag into the attribute array [eg. checked]
458457
if ($fromSpace !== '/') {
459-
$attr = StringHelper::substr($fromSpace, 0, $nextSpace);
458+
$attr = substr($fromSpace, 0, $nextSpace);
460459
}
461460
}
462461

@@ -469,8 +468,8 @@ protected function cleanTags($source)
469468
$attrSet[] = $attr;
470469

471470
// Move search point and continue iteration
472-
$tagLeft = StringHelper::substr($fromSpace, StringHelper::strlen($attr));
473-
$currentSpace = StringHelper::strpos($tagLeft, ' ');
471+
$tagLeft = substr($fromSpace, strlen($attr));
472+
$currentSpace = strpos($tagLeft, ' ');
474473
}
475474

476475
// Is our tag in the user input array?
@@ -489,7 +488,7 @@ protected function cleanTags($source)
489488
}
490489

491490
// Reformat single tags to XHTML
492-
if (StringHelper::strpos($fromTagOpen, '</' . $tagName)) {
491+
if (strpos($fromTagOpen, '</' . $tagName)) {
493492
$preTag .= '>';
494493
} else {
495494
$preTag .= ' />';
@@ -501,8 +500,8 @@ protected function cleanTags($source)
501500
}
502501

503502
// Find next tag's start and continue iteration
504-
$postTag = StringHelper::substr($postTag, ($tagLength + 2));
505-
$tagOpenStart = StringHelper::strpos($postTag, '<');
503+
$postTag = substr($postTag, ($tagLength + 2));
504+
$tagOpenStart = strpos($postTag, '<');
506505
}
507506

508507
// Append any code after the end of tags and return
@@ -659,39 +658,37 @@ protected function escapeAttributeValues($source)
659658
// Process each portion based on presence of =" and "<space>, "/>, or ">
660659
// See if there are any more attributes to process
661660
while (preg_match('#<[^>]*?=\s*?(\"|\')#s', $remainder, $matches, \PREG_OFFSET_CAPTURE)) {
662-
// We have found a tag with an attribute, convert its byte position to a UTF-8 string length, using non-multibyte substr()
663661
$stringBeforeTag = substr($remainder, 0, $matches[0][1]);
664-
$tagPosition = StringHelper::strlen($stringBeforeTag);
662+
$tagPosition = strlen($stringBeforeTag);
665663

666664
// Get the character length before the attribute value
667-
$nextBefore = $tagPosition + StringHelper::strlen($matches[0][0]);
665+
$nextBefore = $tagPosition + strlen($matches[0][0]);
668666

669667
// Figure out if we have a single or double quote and look for the matching closing quote
670668
// Closing quote should be "/>, ">, "<space>, or " at the end of the string
671-
$quote = StringHelper::substr($matches[0][0], -1);
669+
$quote = substr($matches[0][0], -1);
672670
$pregMatch = ($quote == '"') ? '#(\"\s*/\s*>|\"\s*>|\"\s+|\"$)#' : "#(\'\s*/\s*>|\'\s*>|\'\s+|\'$)#";
673671

674672
// Get the portion after attribute value
675-
$attributeValueRemainder = StringHelper::substr($remainder, $nextBefore);
673+
$attributeValueRemainder = substr($remainder, $nextBefore);
676674

677675
if (preg_match($pregMatch, $attributeValueRemainder, $matches, \PREG_OFFSET_CAPTURE)) {
678-
// We have a closing quote, convert its byte position to a UTF-8 string length, using non-multibyte substr()
679676
$stringBeforeQuote = substr($attributeValueRemainder, 0, $matches[0][1]);
680-
$closeQuoteChars = StringHelper::strlen($stringBeforeQuote);
677+
$closeQuoteChars = strlen($stringBeforeQuote);
681678
$nextAfter = $nextBefore + $closeQuoteChars;
682679
} else {
683680
// No closing quote
684-
$nextAfter = StringHelper::strlen($remainder);
681+
$nextAfter = strlen($remainder);
685682
}
686683

687684
// Get the actual attribute value
688-
$attributeValue = StringHelper::substr($remainder, $nextBefore, $nextAfter - $nextBefore);
685+
$attributeValue = substr($remainder, $nextBefore, $nextAfter - $nextBefore);
689686

690687
// Escape bad chars
691688
$attributeValue = str_replace($badChars, $escapedChars, $attributeValue);
692689
$attributeValue = $this->stripCssExpressions($attributeValue);
693-
$alreadyFiltered .= StringHelper::substr($remainder, 0, $nextBefore) . $attributeValue . $quote;
694-
$remainder = StringHelper::substr($remainder, $nextAfter + 1);
690+
$alreadyFiltered .= substr($remainder, 0, $nextBefore) . $attributeValue . $quote;
691+
$remainder = substr($remainder, $nextAfter + 1);
695692
}
696693

697694
// At this point, we just have to return the $alreadyFiltered and the $remainder

0 commit comments

Comments
 (0)