Filtering User Input
Every value that arrives from a browser is read through Filter. It cuts the value down to the characters the receiving code expects, before anything else sees it.
Overview
WISECP never reads $_POST, $_GET or $_REQUEST directly. One class takes a path into the superglobal plus a filter name. It answers the value already reduced to what that filter allows.
Filtering is half of input safety. Validation is the other half: filtering removes characters, validation decides whether what is left is usable. A filtered but meaningless value still has to be rejected by your own check.
Reference
Signatures
public static function init($arg = null, $mod = false, $special = false);
public static function GET($arg = '');
public static function POST($arg = '');
public static function REQUEST($arg = '');
public static function FILES($arg = '');
public static function SERVER($arg = '');
public static function html_clear($arg = null, $allow = '');
public static function phone($arg = '');
public static function route($arg = '', $special = '');
public static function permalink($str, $options = []);
Everything here is static. Most names in the table below are also methods of their own with the same two arguments. Filter::rnumbers($value) does what init does after resolving the path. Three are different. There is no hclear method, because that name calls html_clear. false is the absence of a filter, and domain takes the value alone.
The Three Arguments of init
POST/user/name, or a plain value. A string that starts with none of the five source names is filtered as it stands, so Filter::init($text, "hclear") is legal. An empty first argument answers false.
false returns the value untouched, and so does an unrecognised name.
a-z is a range, and a literal hyphen or slash needs escaping. With $mod left at false it becomes the whole allow list, applied after markup is stripped.
Source Paths
The first path segment names the superglobal, the rest walks into it. POST/user/name reads the name key inside the user array of the request body. The same five accessors are public and return the raw value, unfiltered. With no argument they return the whole superglobal; with a missing key they return false, never null.
GET/.
POST/.
REQUEST/, and how an operation name is read.
FILES/.
REMOTE_ADDR. Path prefix SERVER/.
Filter Types
The last column is what you get when the key is not in the request. That answer differs by filter.
| Filter | Keeps | Returns | Key absent |
|---|---|---|---|
false | The value untouched, markup included | mixed | false |
hclear | Tags stripped after entities are decoded | string | empty string |
text | Same, then both quote characters become numeric entities | string | empty string |
dtext | Same as hclear, quotes left alone | string | empty string |
letters_numbers | Latin letters and digits | string | false |
letters | Latin letters plus the active language's letters | string | empty string |
numbers | Digits and hyphen | string | false |
rnumbers | Digits and hyphen, then cast to a whole number | int | 0 |
amount | Digits, hyphen, dot, comma; separators kept as typed | string | empty string |
rate | Same, comma read as the decimal point, thousands dots dropped | float | 0.0 |
ip | Letters, digits, hyphen, dot, colon, so v6 survives | string | false |
domain | Letters, digits, dot, hyphen, then lowercased | string | empty string |
email | Letters, digits and the characters @ . + _ | string | empty string |
route | Letters, digits, hyphen, underscore, dot; ../ removed first | string | empty string |
folder | Letters, digits, slash, hyphen, underscore, dot | string | empty string |
file | Letters, language letters, digits, hyphen, underscore, dot | string | empty string |
noun | Letters, digits, comma, dot, space | string | empty string |
identity | Digits and hyphen, same rule as numbers | string | false |
password | Everything, unchanged | mixed | false |
password is deliberately a pass-through. A strong password is made of exactly the characters the other filters remove. Stripping them silently changes what the visitor typed. hclear, text, dtext, email, domain, identity and password ignore the third argument.
Helpers You Call Directly
'<b><i>'. This is the only way to keep tags: hclear never passes one.
../, then keeps letters, digits, hyphen, underscore and dot. Add the escaped slash through the second argument when a route segment carries one.
delimiter (a hyphen), limit (none), lowercase (true), replacements (patterns applied first, empty) and transliterate (true).
Example
Field names are the contract between the two sides. What the form posts is what the path spells.
<input type="hidden" name="operation" value="update_client">
<input type="hidden" name="id" value="42">
<input type="text" name="user[name]">
<input type="email" name="email">
<input type="password" name="password">
<input type="text" name="slug">
public function update_client(Operation $operation): bool
{
$operation->demo();
$id = (int) Filter::init("POST/id", "rnumbers");
$name = Filter::init("POST/user/name", "hclear");
$email = Filter::init("POST/email", "email");
$pass = Filter::init("POST/password", "password");
$slug = Filter::init("POST/slug", "route", "\/");
// Filtering is not validation: these are clean, not necessarily usable.
if (!$id) throw new Exception(Language::gc("error/id-required"));
if ($name === '') throw new Exception(Language::gc("error/name-required"));
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) throw new Exception(Language::gc("error/email-invalid"));
$bio = Filter::html_clear(Filter::POST("bio"), '<b><i><a>');
return $operation->output(['status' => "successful"]);
}
Pitfalls
The name is matched against a list and anything unmatched falls through to the untouched value, markup and all. Filter::init("POST/v", "number") is not a numeric read, it is no read at all. Writing the allow list into the second slot instead of the third does the same: Filter::init("POST/v", "a-z") filters nothing.
The five accessors always answer false, so a presence test is written !== false. Through init the answer belongs to the filter, as the table's last column shows. A test written as !== '' or !== null is true on every request for some of them.
It is pasted into a character class exactly as written. It can widen the filter far past what you meant, and a stray bracket breaks the pattern. Escape what must be literal, and never build it from user input.
Only password and the unfiltered read pass an array through. The rest answer false, an empty string or zero depending on the filter. A grouped field is read key by key rather than as one value.
Related Articles
Vielen Dank für Ihre Rückmeldung!
Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.