PHP utility collection with hybrid and fluent APIs.

βŒˆβŒ‹ βŽ‡ branch:  hybrid7 libraries


Update of "input"

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview

Artifact ID: aaf9063513c9902a75d3b5b46f33e283ecca9c2b
Page Name:input
Date: 2014-03-20 00:16:09
Original User: mario
Mimetype:text/x-markdown
Parent: a6173bf801bcd22adb63d7f79acda84eeb133315 (diff)
Next 0700301563bb6552982ecd7cf6fb98c1cd673c36
Content

new input()

The input class wraps the superglobals $_REQUEST, $_GET, $_POST, $_SERVER and $_COOKIE. It provides streamlined sanitization with unobtrusive filter names and a unique semi-fluent syntax:

   $_REQUEST->text["content"]

Filtering functions can also be chained (i.e. $_GET->words->html["title"]). There are whitelisting and sanitizing methods. And global functions can be utilized as well.

  • This approach addresses input constraint validation at the earliest feasible entry point.

  • Unifies access through a central verification mechanism.

  • Allows reliable input interpolation instantly in and into many target contexts.

Additionally it can still shadow/audit casual and unverfied accesses. Primarily its API overall simplicity is to encourage safety with minimal effort.

Available filters

There's a wide range of built-in methods. Often the first few basic filters are sufficient and best suited for combination.

Method Type Sample Usage
int cast 123 Only numeric characters, cast to integer.
name white abc12_x3 Alphanumeric symbols only.
id white xy_2.1 Alphanumeric chars, dot and underscore.
words white abc def Text with minimal interpunction (only spaces allowed).
text white Hello, World! Common natural text with basic interpunction (including quotes, but no < >).
filename filter basename.txt Replace all non-alphanumeric characters with underscores.
float cast 3.14159 Cast to float.
boolean cast true, false Converts "false/true" or "0/1" or "off/on" and "no/yes" to boolean.
ascii white Aa#:β€œ,n0~ Characters in the ASCII range 0 .. 127
nocontrol white Aa#:β€œ,n~ Fiilters out control characters (< 32), except r n t.
spaces filter Single line Turns linebreaks / whitespace (r n t) into spaces only.
q black β€œvalueβ€œ Shorthand for addslashes.
escape black []β€œ{}'$!Β΄&amp;?/&gt;&lt;|*~;^</td> <td>Broader escaping of well-known meta charactes (quotes and regex).</td> </tr> <tr> <td>html</td> <td>filter</td> <td>&amp;amp;</td> <td>htmlspecialchars (shorthand)</td> </tr> <tr> <th>Structural</th> <th colspan=3>Following filters constrain specific input formats.</th> </tr> <tr> <td>datetime</td> <td>white</td> <td>1999-12-31T23:59:59Z</td> <td>HTML5 datetime values</td> </tr> <tr> <td>date</td> <td>white</td> <td sdval="42202" sdnum="1031;0;JJJJ-MM-TT">2015-07-17</td> <td>Just date string.</td> </tr> <tr> <td>time</td> <td>white</td> <td>23:59:20.17</td> <td>Time specifier.</td> </tr> <tr> <td>color</td> <td>white</td> <td>#FF5022</td> <td>Hex color value.</td> </tr> <tr> <td>tel</td> <td>white</td> <td>&quot;+1-347-2214144</td> <td>International-format telephone number.</td> </tr> <tr> <td>iconv</td> <td>filter</td> <td><br></td> <td>Convert input to UTF-8</td> </tr> <tr> <td>utf7</td> <td>black</td> <td><br></td> <td>Filter some UTF-7 out.</td> </tr> <tr> <td>ip</td> <td>white</td> <td>::1</td> <td>IPv4 or IPv6 address</td> </tr> <tr> <td>ipv4</td> <td>white</td> <td>134.22.7.207</td> <td>IPv4 address only</td> </tr> <tr> <td>public</td> <td>white</td> <td>8.8.4.4</td> <td>Validate IP to be public.</td> </tr> <tr> <td>email</td> <td>white</td> <td>you @gmail.com</td> <td>Syntactically valid email address.</td> </tr> <tr> <td>url</td> <td>white</td> <td><br></td> <td>Ensure URL syntax xxx:///</td> </tr> <tr> <td>http</td> <td>white</td> <td>http:// localhost/</td> <td>More conservative http:// URL constraint.</td> </tr> <tr> <td>uri</td> <td>white</td> <td><br></td> <td>More generic URI syntax.</td> </tr> <tr> <td>xml</td> <td>cast</td> <td><br></td> <td>Create a SimpleXML object from input.</td> </tr> <tr> <td>json</td> <td>cast</td> <td>{β€žkeyβ€œ:β€œvalueβ€œ}</td> <td>json_decode()</td> </tr> <tr> <td>purify</td> <td>filter</td> <td>&lt;b&gt;basic&lt;/b&gt;</td> <td>Utilizes HTMLPurifier</td> </tr> <tr> <th>NOP</th> <th colspan=3>Virtual / control filters.</th> </tr> <tr> <td>log</td> <td>control</td> <td><br></td> <td>Raw value access with logging.</td> </tr> <tr> <td>raw</td> <td>control</td> <td><br></td> <td>Raw access with E_NOTICE (is the default).</td> </tr> <tr> <td>disallow</td> <td>control</td> <td><br></td> <td>Disallow unfiltered variable access (configurable per INPUT_DIRECT).</td> </tr> <tr> <td>is</td> <td>control</td> <td><br></td> <td>Is a meta filter, that applies the following filter chain, then checks if the content would have passed unaffected. Returns a boolean if all constraints were matched.</td> </tr> <tr> <th><b>Parameterized</b></td> <th colspan=3>These filters require method access$_GET->default(β€židβ€œ, β€žindexβ€œ)instead of the plain array key syntax.</th> </tr> <tr> <td>length(ID, 20)</td> <td>filter</td> <td>Hello Wo</td> <td>Cuts strings to maximum given length.</td> </tr> <tr> <td>range(ID, 1, 15)</td> <td>white</td> <td>17</td> <td>Constrains numeric input to the given range.</td> </tr> <tr> <td>default</td> <td>filter</td> <td>…</td> <td>Uses default value, if no input present.</td> </tr> <tr> <td>regex</td> <td>white/black</td> <td>…</td> <td>Custom regular expression method -&gt;regex(&quot;field&quot;, &quot;/(abc)/&quot;)</td> </tr> <tr> <td>in_array</td> <td>white</td> <td>a,b,c</td> <td>Can be used with array parameter, or a simpler comma-separated of allowed values.</td> </tr> <tr> <td><br></td> <td><br></td> <td><br></td> <td><br></td> </tr> <tr> <th>Multi-Apply</th> <th colspan=3>Following filters work on a set of input variables, instead of a single one.</th> </tr> <tr> <td>array</td> <td>control</td> <td><br></td> <td>Is automatically applied to input subarrays, so filters are run on each entry.</td> </tr> <tr> <td>list</td> <td>control</td> <td><br></td> <td>Combine multiple input variables per name (comma-separated list) and apply filtering collectively; finally return a named result array.</td> </tr> <tr> <td>multi</td> <td>control</td> <td><br></td> <td>Also grabs a list of input variables. But does not run filters on scalars within, but pass the combined set to filter functions. This is used in combination with e.g.http_build_query</td> </tr> <tr> <th>Global functions</th> <th colspan=3><br></th> </tr> <tr> <td>strtolower</td> <td>filter</td> <td><br></td> <td rowspan=3 align="justify">Any global function can be utilized, if it simply modifies or filters a single input string or value.</td> </tr> <tr> <td>urlencode</td> <td>filter</td> <td><br></td> </tr> <tr> <td>strip_tags</td> <td>filter</td> <td><br></td> </tr> <tr> <td><br></td> <td><br></td> <td><br></td> <td><br></td> </tr> <tr> <th>Inadvised filters</th> <th colspan=3>Care should be taken with following filters. Liberal application will lead to a false sense of security.</th> </tr> <tr> <td>sql</td> <td>filter</td> <td><br></td> <td>ConfigurablePDO::quoteshorthand.</td> </tr> <tr> <td>mysql</td> <td>filter</td> <td><br></td> <td>Shorthand tomysql_real_escape_string` (doubly discouraged).
xss black
Minimal XSS blacklist

As mentioned, any global function can be utilized implicitly. A few core string functions are useful in this context. But the intended target are custom functions.

Binding filters

One can even bind new functions or class methods using:

 $_GET->_filtername = array("AppFilter", "validSessionID");

It's imperative to shadow the filternames using an underscore _ prefix however. See for example input.inspekt.php for some examples. This allows them to be chained still:

 $_GET->text->validSessionID["var"]

(Btw, to use some of the input filter methods statically and outside of their scope, one could use $value = input::_datetime($value); for instance.)

Complex filters

With ->list and ->multi you can utilize some more crafty features. For instance:

 $_GET->multi->http_build_query["id,name,title"]

Will rebuild an URL-encoded string from three input variables.

Wrapper implementation

Basically the filters are initialized for all superglobals like:

$_GET = new input($_GET);

The original variables are stored in ->__vars[] internally. Each $_GET->filtername pseudo-method access is accumulated in a filter chain.

The first use of array ["key"] or method ("key") requests, applies the filter chain to the named input variable, then returns the constrained value.

Filter chain defaults

It's possible to define a default filter for remaining $_GET["old"] accesses with the INPUT_DIRECT constant.

  • Per default it uses "raw" which just prints a notice.
  • It can also be set to "disable" to prevent such uses.
  • Another alternative would be "q" to emulate magic quotes (not recommended).
  • Or using "sql" to securely use $_POST["fields"] in SQL strings, if that's the default target (also not recommended).

Another option is to predefine a filter chain on a particular superglobal with ->always():

$_POST->xss->nocontrol->always();

Then any $_RAW["access"] would still use these filters. Yet additional more context-specific filters could also be intermixed.

It's equivalent to having the filter chain built up, before accessing an entry:

$_GET->filter->name->and->more;
$_GET["var"]

Btw, to reset a default filter chain, use ->__always = array();

Predeclaring filters for raw access

While this somewhat amounts to magic_quotes 2.0, you can also pre-define filter chains on a variable name basis:

$_GET->__rules["old_id"] = array("int", array());

This is suitable for bolting a minimum of safety onto old code, whose data flow is structurally hard to fix otherwise.

Differences to plain $_GET / $_POST / $_REQUEST </h3>

Because the whole ArrayAccess and Iterator interfaces are implemented, it's easy to transition existing code to new input(). There are few behavioural discrepancies.

One thing that won't work however is the common / olden idiom:

if ($_POST) {

To probe for presence of input data, one should check one of the keys, or rather:

if (count($_POST)) {

Which has the same effect.

->has() ->no() ->keys()

These three convenience methods make some idioms more readble. Instead of testing for isset($_GET["key"]) one can now write: $_GET->has("key"). Or to probe for the opposite $_GET->no("sleep").

In place of array_keys() there's now $_REQUEST->keys(), also slightly shorter.

Notice emission

Syntactic salt ala isset($_GET["id"]) ? $_GET["id"] : "" for silent value substitution has become commonplace.

It's made redundant here, because input{} itself already probes for existence of variables. Notices for absent values are only generated afterwards, and only if requested. Thus they can be reenabled when needed, unlike with the isset and ?: supper suppression syntax.

INPUT_DIRECT controls the default filter for $_GET["raw"] access. If it's set to raw then this specific filter name will engage. And raw honors INPUT_SILENCE. Per default it still emits useful notices. If set to 1 it will no more.

Rewritten code can default to $_REQUEST->raw->default("id", 123) however. This combines both the default value substitution, but still permits bringing back notices and hence debugging.