PHP 8.4.26 Released!
+add a note

User Contributed Notes 4 notes

up
13
shaman_master at list dot ru
6 years ago
Note: FILTER_NULL_ON_FAILURE, FILTER_REQUIRE_SCALAR, FILTER_REQUIRE_ARRAY and FILTER_FORCE_ARRAY dont have prefix "FILTER_FLAG_" but they are FLAGS!
up
1
steven at nevvix dot com
7 months ago
<?php
+    // Handle FILTER_FLAG_STRIP_BACKTICK
+    // Fixes bug PHP_VERSION < 5.5.24
+    if ($flags & FILTER_FLAG_STRIP_BACKTICK) {
+        $output = str_replace('`', '', $output);
+    }
?>

Revised version:
<?php
function filter_sanitize_string(mixed $value, int $flags = 0): string|false|null {
    if ($flags & FILTER_FLAG_EMPTY_STRING_NULL && $value === "") {
        return null;
    }
    if (!(is_scalar($value) || is_null($value))) {
        return false;
    }

    // Strip HTML tags and remove NUL bytes
    $value = (string)$value;
    $value = strip_tags($value);
    $value = str_replace("\0", "", $value);

    // High/Low ASCII handling (Byte-by-byte to match legacy behavior)
    $output = "";
    for ($i = 0, $n = strlen($value); $i < $n; $i++) {
        $char = $value[$i];
        $ord = ord($char);

        if ($ord < 32) {
            if ($flags & FILTER_FLAG_STRIP_LOW) continue;
            if ($flags & FILTER_FLAG_ENCODE_LOW) {
                $output .= "&#$ord;"; // Legacy decimal encoding
                continue;
            }
        }

        if ($ord > 127) {
            if ($flags & FILTER_FLAG_STRIP_HIGH) continue;
            if ($flags & FILTER_FLAG_ENCODE_HIGH) {
                $output .= "&#$ord;"; // This turns © into &#194;&#169; (©)
                continue;
            }
        }

        $output .= $char;
    }

    // Strip backticks
    // Fixes bug PHP_VERSION < 5.5.24
    if ($flags & FILTER_FLAG_STRIP_BACKTICK) {
        $output = str_replace('`', '', $output);
    }

    // Legacy ONLY encoded ampersands if FILTER_FLAG_ENCODE_AMP was set
    if ($flags & FILTER_FLAG_ENCODE_AMP) {
        $output = str_replace('&', '&amp;', $output);
    }

    // Replicate legacy quote encoding exactly (&#39; and &#34;)
    if (!($flags & FILTER_FLAG_NO_ENCODE_QUOTES)) {
        $output = str_replace(["'", '"'], ['&#39;', '&#34;'], $output);
    }

    return $output;
}
up
1
steven at nevvix dot com
7 months ago
We can add FILTER_FLAG_EMPTY_STRING_NULL:
<?php
- function filter_sanitize_string(mixed $value, int $flags = 0): string|false {
+ function filter_sanitize_string(mixed $value, int $flags = 0): string|false|null {
+     if ($flags & FILTER_FLAG_EMPTY_STRING_NULL && $value === "") {
+         return null;
+    }
?>

For FILTER_FLAG_STRIP_BACKTICK, there is a bug history
(https://bugs.php.net/bug.php?id=69202)
so I did not attempt to code compatility through version history.
up
1
steven at nevvix dot com
7 months ago
We're all looking for a polyfill to replace the FILTER_SANITIZE_STRING flag, deprecated as of PHP 8.1.0,
that behaves exactly the same for scalar and non-scalar values.
(Using htmlspecialchars() and strip_tags() are not returning exactly the same output.)

<?php
function filter_sanitize_string(mixed $value, int $flags = 0): string|false {
    if (!(is_scalar($value) || is_null($value))) {
        return false;
    }

    // Strip HTML tags and remove NUL bytes
    $value = (string)$value;
    $value = strip_tags($value);
    $value = str_replace("\0", "", $value);

    // High/Low ASCII handling (Byte-by-byte to match legacy behavior)
    $output = "";
    for ($i = 0, $n = strlen($value); $i < $n; $i++) {
        $char = $value[$i];
        $ord = ord($char);

        if ($ord < 32) {
            if ($flags & FILTER_FLAG_STRIP_LOW) continue;
            if ($flags & FILTER_FLAG_ENCODE_LOW) {
                $output .= "&#$ord;"; // Legacy decimal encoding
                continue;
            }
        }

        if ($ord > 127) {
            if ($flags & FILTER_FLAG_STRIP_HIGH) continue;
            if ($flags & FILTER_FLAG_ENCODE_HIGH) {
                $output .= "&#$ord;"; // This turns © into &#194;&#169; (©)
                continue;
            }
        }

        $output .= $char;
    }

    // Legacy ONLY encoded & if FILTER_FLAG_ENCODE_AMP was set.
    if ($flags & FILTER_FLAG_ENCODE_AMP) {
        $output = str_replace('&', '&amp;', $output);
    }

    // Replicate legacy quote encoding exactly (&#39; and &#34;)
    if (!($flags & FILTER_FLAG_NO_ENCODE_QUOTES)) {
        $output = str_replace(["'", '"'], ['&#39;', '&#34;'], $output);
    }

    return $output;
}

// Tests
$string = "It's a \"test\" \x02 <script> > & ©";

// Deprecated example:
$sanitized = filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_ENCODE_HIGH);
var_dump($sanitized);

// Polyfill example:
$sanitized = filter_sanitize_string($string, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_ENCODE_HIGH);
var_dump($sanitized);

// Polyfill in filter_var() callback:
$sanitized = filter_var($string, FILTER_CALLBACK, [
    'options' => function($value) {
        return filter_sanitize_string($value, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_ENCODE_HIGH);
    }
]);
var_dump($sanitized);
?>

Output:
string(44) "It&#39;s a &#34;test&#34;   > & &#194;&#169;"
string(44) "It&#39;s a &#34;test&#34;   > & &#194;&#169;"
string(44) "It&#39;s a &#34;test&#34;   > & &#194;&#169;"
To Top