Filter URL query variables
Superglobals vs filter_input
$_GET & $_POST superglobals
Snapshot array of the URL query in the GET/ POST response after the HTTP response is completed.
filter_input()
filter_input()
gets its value directly from the HTTP response, not from superglobals $_GET
or $_POST
.
★ They don't modify each other.
Sanitization
If the query ?store_id=...
initially returns a corrupt value, then gets sanitized, $_POST['store_id']
still contains the corrupt value.
$store_id = filter_input(INPUT_POST, 'store_id', FILTER_SANITIZE_NUMBER_INT);
var_dump($store_id === $_POST['store_id']);
bool(false);
filter_input
and$_POST
&$_GET
all return 'string' type values (or arrays of strings).
How can you test a corrupt value without using a URL in a browser and risking corrupting your application?
To test in a PHP console without using filter_input()
because you don't have an available HTTP response, use filter_var()
which processes the provided variable exactly the same.
Replace:
filter_input(INPUT_POST, ‘store_id’, FILTER_SANITIZE_STRING);
with:
filter_var($store_id, FILTER_SANITIZE_STRING);
Example console tests
$store_id = "<script>alert('Yikes!');</script>";
$store_id = filter_var($store_id, FILTER_SANITIZE_STRING);
var_dump($store_id);
string(24) "alert('Yikes!');"
$store_id = "<script>alert('Yikes!');</script>";
$store_id = filter_var($store_id, FILTER_SANITIZE_NUMBER_INT);
var_dump($store_id);
string(0) ""
$store_id = "<script>alert('30');</script>";
$store_id = filter_var($store_id, FILTER_SANITIZE_NUMBER_INT);
var_dump($store_id);
string(2) "30"
Future compatibility
FILTER_SANITIZE_STRING
and FILTER_SANITIZE_STRIPPED
are deprecated as of PHP v8.1.0. The replacement is:
$store_id = "<script>alert('30');</script>";
$store_id = filter_var($store_id, FILTER_CALLBACK, [
'options' => function ($value) {
return htmlspecialchars(strip_tags($value), ENT_QUOTES);
}
]);
// A closure returns a syntax error in the silly console. Try a one-liner:
$store_id = filter_var($store_id, FILTER_CALLBACK, ['options' => function ($value) {return htmlspecialchars(strip_tags($value), ENT_QUOTES);}]);
var_dump($store_id);
string(22) "alert('30');"
You can define a custom callback separately:
function sanitize_callback($value) {
return htmlspecialchars(strip_tags($value), ENT_QUOTES);
}
$store_id = "<script>alert('30');</script>";
$store_id = filter_var($store_id, FILTER_CALLBACK, ['options' => 'sanitize_callback']);
var_dump($store_id);
string(22) "alert('30');"
Validation
After sanitization, time for validation of safe values.
That’s when you use filter_var()
straight in your code.
Test integer
$store_id = "30";
$store_id = filter_var($store_id, FILTER_VALIDATE_INT, [
'options' => [
'min_range' => 1,
'max_range' => 20000,
]
]);
var_dump($store_id);
int(30)
The type is converted to ‘int’ as intended.
Test hexadecimal digits
$invoice_code = "3f4e2210dc87b";
$invoice_code = filter_var($invoice_code, FILTER_CALLBACK, [
'options' => function ($value) {
return ctype_xdigit($value) ? $value : false;
}
]);
// A closure returns a syntax error in the silly console. Try a one-liner:
$invoice_code = filter_var($invoice_code, FILTER_CALLBACK, ['options' => function ($value) {return ctype_xdigit($value) ? $value : false;}]);
var_dump($invoice_code);
string(13) "3f4e2210dc87b"
Invalid value:
$invoice_code = "ZZ3f4e2210dc87b";
$invoice_code = filter_var($invoice_code, FILTER_CALLBACK, ['options' => function ($value) {return ctype_xdigit($value) ? $value : false;}]);
var_dump($invoice_code);
bool(false)
You can define a custom callback separately:
function validate_callback($value) {
return ctype_xdigit($value) ? strtoupper($value) : false;
}
$invoice_code = "3f4e2210dc87b";
$invoice_code = filter_var($invoice_code, FILTER_CALLBACK, ['options' => 'validate_callback']);
var_dump($invoice_code);
string(13) "3F4E2210DC87B"
Difference in callbacks
Notice the callback return value is different in sanitization vs validation.
Sanitization
To be consistent with all the sanitization filters, return the value itself after processing.
return htmlspecialchars(strip_tags($value), ENT_QUOTES);
Validation
To be consistent with all the validation filters, return the processed value if valid, else return false.
return ctype_xdigit($value) ? $value : false;
Currency example
Sanitize
$amount = "<script>alert('2,000.00');</script>";
$amount = filter_var($amount, FILTER_SANITIZE_NUMBER_FLOAT, [
'flags' => FILTER_FLAG_ALLOW_THOUSAND | FILTER_FLAG_ALLOW_FRACTION
]);
var_dump($amount);
string(8) "2,000.00"
To display a 'float' type value, you need to keep it formatted as a ‘string’.
Validate
To save in a database, convert it to ‘float’ ('double').
$amount = "2,000.00";
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT);
var_dump($amount);
bool(false)
$amount = "2,000.00";
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, ['flags' => FILTER_FLAG_ALLOW_THOUSAND]);
var_dump($amount);
double(2000)
$amount = "2,000.01";
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, ['flags' => FILTER_FLAG_ALLOW_THOUSAND]);
var_dump($amount);
double(2000.01)
// Assume a German amount
$amount = "2.000,01";
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, [
'options' => ['decimal' => ','],
'flags' => FILTER_FLAG_ALLOW_THOUSAND,
]);
var_dump($amount);
double(2000.01)
Array examples
Using filter_input_array()
and filter_var_array()
.
Sanitize
To apply one filter to all values:
$array = ['<a href="#">link</a>', '30', ';go'];
$array = filter_var_array($array, FILTER_SANITIZE_STRING);
var_dump($array);
array(3) {
[0] =>
string(4) "link"
[1] =>
string(2) "30"
[2] =>
string(3) ";go"
}
To apply one filter to each of the query variables, assuming the URL query is:
?store_name=<a href="#">Foxtrot</a>&store_id=30&invoice_code=;go
$url_query = filter_input_array(INPUT_GET, [
'store_name' => FILTER_SANITIZE_STRING,
'store_id' => FILTER_SANITIZE_NUMBER_INT,
'invoice_code'=> FILTER_SANITIZE_STRING,
]);
Test in console:
$url_query = [
'store_name' => '<a href="#">Foxtrot</a>',
'store_id' => '30',
'invoice_code'=> ';go',
];
$url_query = filter_var_array($url_query, [
'store_name' => FILTER_SANITIZE_STRING,
'store_id' => FILTER_SANITIZE_NUMBER_INT,
'invoice_code'=> FILTER_SANITIZE_STRING,
]);
var_dump($url_query);
array(3) {
'store_name' =>
string(4) "Foxtrot"
'store_id' =>
string(2) "30"
'invoice_code' =>
string(3) ";go"
}
Validate
function validate_callback($value) {
return ctype_xdigit($value) ? strtoupper($value) : false;
}
$url_query = filter_var_array($url_query, [
'store_name' => [
'filter' => FILTER_VALIDATE_REGEXP,
'options' => ['regexp' => '/^[Ff]/'],
],
'store_id' => FILTER_VALIDATE_INT,
'invoice_code'=> [
'filter' => FILTER_CALLBACK,
'options' => 'validate_callback',
],
]);
var_dump($url_query);
array(3) {
'store_name' =>
string(4) "Foxtrot"
'store_id' =>
int(30)
'invoice_code' =>
bool(false)
}