Variable MB_SANITIZE_PHPConst
MB_SANITIZE_PHP: "\n/**\n * Replaces every ill-formed UTF-8 sequence with \"?\".\n *\n * Matches native mbstring byte for byte, and the rule is NOT one \"?\" per bad\n * byte -- that was measured, not assumed. Native emits ONE \"?\" per maximal valid\n * PREFIX it consumed, then resumes at the byte that broke the sequence:\n *\n * $sub exists for one caller: mb_convert_case TITLE, which needs the substituted run to\n * be a case-IGNORABLE character rather than \"?\" while the word boundaries are found.\n *\n * \"abc\\xe4\\xbddef\" -> abc?def one \"?\", two bytes consumed\n * \"abc\\xe4\\xbd\" -> abc? same at end of string\n * \"abc\\xed\\xa0\\x80def\" -> abc???def ED is a valid lead but A0 is out of its\n * range, so ED alone is one \"?\" and A0 and\n * 80 are then lone continuations\n * \"abc\\xc0\\xafdef\" -> abc??def C0 is never a valid lead\n * \"abc\\xf5\\x80\\x80\\x80def\" -> abc????def\n *\n * A first version advanced one byte at a time on failure and got the truncated\n * cases wrong (abc?? for abc?). The oracle is native mb_substr(); the gate test\n * pins every case above against it.\n *\n * @param mixed $s\n * The string to clean; anything else is returned as it came.\n * @param string $sub\n * What replaces each ill-formed run.\n *\n * @return mixed\n * The cleaned string, or the input when it was not a string.\n */\nfunction cfw_mb_sanitize($s, $sub = '?')\n{\n\tif (!is_string($s) || $s === '') {\n\t\treturn $s;\n\t}\n\t// fast path: already well-formed\n\tif (preg_match('//u', $s) === 1) {\n\t\treturn $s;\n\t}\n\n\t$out = '';\n\t$len = strlen($s);\n\t$i = 0;\n\twhile ($i < $len) {\n\t\t$b = ord($s[$i]);\n\t\tif ($b < 0x80) {\n\t\t\t$out .= $s[$i];\n\t\t\t$i++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// per-lead continuation ranges; the second byte carries the overlong and\n\t\t// surrogate bounds, which is why this is not a flat 0x80-0xBF test\n\t\tif ($b >= 0xc2 && $b <= 0xdf) {\n\t\t\t$need = 1;\n\t\t\t$lo1 = 0x80;\n\t\t\t$hi1 = 0xbf;\n\t\t} elseif ($b === 0xe0) {\n\t\t\t$need = 2;\n\t\t\t$lo1 = 0xa0;\n\t\t\t$hi1 = 0xbf;\n\t\t} elseif ($b >= 0xe1 && $b <= 0xec) {\n\t\t\t$need = 2;\n\t\t\t$lo1 = 0x80;\n\t\t\t$hi1 = 0xbf;\n\t\t} elseif ($b === 0xed) {\n\t\t\t$need = 2;\n\t\t\t$lo1 = 0x80;\n\t\t\t$hi1 = 0x9f;\n\t\t} elseif ($b >= 0xee && $b <= 0xef) {\n\t\t\t$need = 2;\n\t\t\t$lo1 = 0x80;\n\t\t\t$hi1 = 0xbf;\n\t\t} elseif ($b === 0xf0) {\n\t\t\t$need = 3;\n\t\t\t$lo1 = 0x90;\n\t\t\t$hi1 = 0xbf;\n\t\t} elseif ($b >= 0xf1 && $b <= 0xf3) {\n\t\t\t$need = 3;\n\t\t\t$lo1 = 0x80;\n\t\t\t$hi1 = 0xbf;\n\t\t} elseif ($b === 0xf4) {\n\t\t\t$need = 3;\n\t\t\t$lo1 = 0x80;\n\t\t\t$hi1 = 0x8f;\n\t\t} else {\n\t\t\t$out .= $sub;\n\t\t\t$i++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t$consumed = 1;\n\t\t$ok = true;\n\t\tfor ($k = 1; $k <= $need; $k++) {\n\t\t\tif ($i + $k >= $len) {\n\t\t\t\t$ok = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t$c = ord($s[$i + $k]);\n\t\t\t$lo = $k === 1 ? $lo1 : 0x80;\n\t\t\t$hi = $k === 1 ? $hi1 : 0xbf;\n\t\t\tif ($c < $lo || $c > $hi) {\n\t\t\t\t$ok = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t$consumed++;\n\t\t}\n\n\t\tif ($ok) {\n\t\t\t$out .= substr($s, $i, $need + 1);\n\t\t\t$i += $need + 1;\n\t\t} else {\n\t\t\t$out .= $sub;\n\t\t\t$i += $consumed;\n\t\t}\n\t}\n\n\treturn $out;\n}\n"