Get the values provided by the user with a method with CSRF validation Remove the verify support as it is not used (and it use eval function which is evil) git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@1371 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
586 lines
21 KiB
PHP
586 lines
21 KiB
PHP
<?php
|
|
/** DomFramework
|
|
@package domframework
|
|
@author Dominique Fournier <dominique@fournier38.fr> */
|
|
|
|
error_reporting (E_ALL);
|
|
/** This class permit to create easily some forms to HTML (or text mode in
|
|
future).
|
|
Each field can be checked in AJAX or HTML. */
|
|
class form
|
|
{
|
|
|
|
/** All the fields */
|
|
private $fields = NULL;
|
|
/** The name of the form */
|
|
private $formName;
|
|
/** Allow to debug the PHP */
|
|
public $debug=0;
|
|
/** CSRF protection
|
|
By default, the CSRF protection is active if a SESSION is active too.
|
|
It can be disabled if needed. An Exception is raised if the form is send
|
|
back without the token */
|
|
public $csrf=TRUE;
|
|
public $csrfField = "CSRF_TOKEN";
|
|
|
|
/** The method used to send the values */
|
|
private $method = "post";
|
|
|
|
/** Create a form
|
|
@param string|null $formName The form name
|
|
*/
|
|
public function __construct ($formName = "form")
|
|
{
|
|
$this->formName = $formName;
|
|
}
|
|
|
|
/** Save the fields into the structure.
|
|
Available :
|
|
- name : name of the field in the HTML page
|
|
- label : label written to the describe the field
|
|
- [titles] : text written in radio/checkboxes
|
|
- [defaults] : default values. Must be array for checkbox/select, and
|
|
string for others
|
|
- [type] : text, password, hidden, checkbox, select, radio, submit
|
|
text by default
|
|
- [multiple] : Multiple selection are possible (if the type supports it)
|
|
- [group] : define a fieldset and define the title with groupe name
|
|
Warning : all the elements of the same group must be
|
|
consecutive !
|
|
- [readonly] : put a read-only flag on the field (the user see it but
|
|
can't interract on it. The value will be sent to next
|
|
page
|
|
- [verify] : Tests to verify with error priority and associated
|
|
message (%s is replaced by field selected value). Order
|
|
test from main tests to minor tests.
|
|
\$tmpfield can be used as a copy of the current field,
|
|
to check the defaults per example
|
|
- [mandatory] : boolean to add a red star at end of label
|
|
|
|
@param array $fields The fields to be displayed
|
|
*/
|
|
public function fields ($fields)
|
|
{
|
|
$this->fields = $fields;
|
|
}
|
|
|
|
/** Return the values provided by the user. Test the CSRF before continue
|
|
NEVER read the values from $_POST in your codes or CSRF will not be
|
|
checked */
|
|
public function values ()
|
|
{
|
|
$values = array ();
|
|
if ($this->method === "post")
|
|
{
|
|
if (isset ($_POST[$this->formName]))
|
|
$values = $_POST[$this->formName];
|
|
}
|
|
elseif ($this->method === "get")
|
|
{
|
|
if (isset ($_GET[$this->formName]))
|
|
$values = $_GET[$this->formName];
|
|
}
|
|
else
|
|
{
|
|
throw new Exception (_("Unknown FORM method (GET or POST allowed)"));
|
|
}
|
|
|
|
if (count ($values) !== 0)
|
|
{
|
|
// CSRF protection
|
|
try
|
|
{
|
|
$this->checkToken ($values[$this->csrfField]);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
throw new Exception ($e->getMessage(), 500);
|
|
}
|
|
// Remove the field CSRF : can not be used outside the form
|
|
unset ($values[$this->csrfField]);
|
|
}
|
|
|
|
return $values;
|
|
}
|
|
|
|
/** Return TRUE if the value associated to a field is correct. Return an
|
|
array with a severity and a message to explain why a field is not
|
|
correct.
|
|
Fields can be an array with just one element, then only this element is
|
|
checked
|
|
@param array $fieldsVerify The fields to verify
|
|
@param array|null $valuesVerify The values of the fields to verify
|
|
*/
|
|
public function verify (&$fieldsVerify, $valuesVerify = NULL)
|
|
{
|
|
// TODO : Remove this function or be cleaner !
|
|
die ("FORM/VERIFY : UNUSED and dirty\n");
|
|
$ret = array ();
|
|
if ($this->debug)
|
|
echo "<pre>";
|
|
foreach ($fieldsVerify as $field)
|
|
{
|
|
if (!isset ($field->verify))
|
|
continue;
|
|
if (!isset ($valuesVerify[$field->name]))
|
|
throw new Exception ("No value provided for $field->name", 500);
|
|
foreach ($field->verify as $test => $message)
|
|
{
|
|
$func = sprintf ($test, addslashes ($valuesVerify[$field->name]));
|
|
if ($this->debug)
|
|
echo "VERIFY: \"$func\" => ";
|
|
$tmpfield = $field;
|
|
$res = addslashes (serialize ($tmpfield));
|
|
// TODO : http://fr2.php.net/manual/en/reflectionfunction.invokeargs.php
|
|
// to remove eval ?
|
|
$rc = eval ("\$tmpfield=unserialize(stripslashes('$res'));".
|
|
"return $func;");
|
|
if ($this->debug)
|
|
var_dump ($rc);
|
|
if ($rc !== FALSE)
|
|
{
|
|
$ret[$field->name] = $message;
|
|
$field->error = $message;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ($this->debug)
|
|
{
|
|
echo "RESULT: ";
|
|
var_dump ($ret);
|
|
echo "</pre>";
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/** Return the fields in HTML code. If $values is provided, use it in place
|
|
of default values. In case of select boxes, $values are the selected
|
|
elements
|
|
$method is the method written in method field of <form>
|
|
@param string|null $method The method to use to transmit the form (POST,
|
|
GET)
|
|
@param array|null $values The default values of the fields */
|
|
public function printHTML ($method = 'post', $values = NULL,
|
|
$errors = array())
|
|
{
|
|
// TODO : textarea, file
|
|
$this->method = strtolower ($method);
|
|
$res = "";
|
|
$res = "<form action='#' method='$method'";
|
|
if ($this->formName != "")
|
|
$res .= " id='$this->formName'";
|
|
$res .= " class='form-horizontal'>\n";
|
|
$group = "";
|
|
foreach ($this->fields as $field)
|
|
{
|
|
if (isset ($field->group) && $field->group !== $group)
|
|
{
|
|
$res .= "<fieldset>\n";
|
|
$res .= " <legend>$field->group</legend>\n";
|
|
$group = $field->group;
|
|
}
|
|
|
|
$res .=" ";
|
|
switch ($field->type)
|
|
{
|
|
case "checkbox":
|
|
// No $field->multiple
|
|
// values !
|
|
$res .= "<div class='form-group";
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " has-".$errors[$field->name][0];
|
|
$res .= "'>\n";
|
|
$res .= " <label class='col-sm-2 control-label' for='".
|
|
$this->formName."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."_0'>";
|
|
$res .= htmlspecialchars ($field->label);
|
|
if (isset ($field->mandatory) && $field->mandatory !== FALSE)
|
|
$res .= " <span style='color:red'>*</span>";
|
|
else
|
|
$res .= " ";
|
|
$res .= "</label>\n";
|
|
$res .= " <div class='col-sm-10'>\n";
|
|
if (is_string ($field->defaults))
|
|
$field->defaults = array ($field->defaults);
|
|
foreach ($field->titles as $key=>$val)
|
|
{
|
|
$res .= " <input type='hidden'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."][$key]'";
|
|
$res .= " value='unset'";
|
|
$res .= "/>";
|
|
$res .= "<input type='checkbox'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."][$key]'";
|
|
$res .= " id='$this->formName"."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."_$key'";
|
|
if (isset ($field->readonly) && $field->readonly !== FALSE)
|
|
$res .= " disabled='disabled'";
|
|
if (isset ($values[$field->name][$key]) &&
|
|
$values[$field->name][$key] !== "unset")
|
|
$res .= " checked='checked'";
|
|
elseif (isset ($field->defaults[$key]) &&
|
|
$field->defaults[$key] !== "")
|
|
$res .= " checked='checked'";
|
|
$res .= " class='form-control'";
|
|
$res .= "/>";
|
|
$res .= "$val\n";
|
|
}
|
|
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " <span class='help-block'>".$errors[$field->name][1].
|
|
"</span>\n";
|
|
$res .= " </div>\n"; // End controls
|
|
$res .= " </div>\n"; // End form-group
|
|
break;
|
|
|
|
case "hidden":
|
|
// No $field->label, $field->multiple, $field->readonly
|
|
$res .= "<input type='hidden'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."]'";
|
|
$res .= " id='$this->formName"."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."'";
|
|
if (isset ($values[$field->name]))
|
|
$res .= " value='".htmlspecialchars ($values[$field->name])."'";
|
|
else
|
|
$res .= " value='".htmlspecialchars ($field->defaults)."'";
|
|
$res .= "/>\n";
|
|
break;
|
|
|
|
case "password":
|
|
// No $field->multiple
|
|
$res .= "<div class='form-group";
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " has-".$errors[$field->name][0];
|
|
$res .= "'>\n";
|
|
$res .= " <label class='col-sm-2 control-label' for='".
|
|
$this->formName."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."'>";
|
|
$res .= htmlspecialchars ($field->label);
|
|
if (isset ($field->mandatory) && $field->mandatory !== FALSE)
|
|
$res .= " <span style='color:red'>*</span>";
|
|
else
|
|
$res .= " ";
|
|
$res .= "</label>\n";
|
|
$res .= " <div class='col-sm-10'>\n";
|
|
$res .= " <input type='password'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."]'";
|
|
$res .= " id='$this->formName"."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."'";
|
|
if (isset ($values[$field->name]))
|
|
$res .= " value='".htmlspecialchars ($values[$field->name],
|
|
ENT_QUOTES)."'";
|
|
else
|
|
$res .= " value='".htmlspecialchars ($field->defaults, ENT_QUOTES).
|
|
"'";
|
|
if (isset ($field->readonly) && $field->readonly !== FALSE)
|
|
$res .= " readonly='readonly'";
|
|
$res .= " class='form-control'";
|
|
$res .= "/>\n";
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " <span class='help-block'>".$errors[$field->name][1].
|
|
"</span>\n";
|
|
$res .= " </div>\n"; // End controls
|
|
$res .= " </div>\n"; // End form-group
|
|
break;
|
|
|
|
case "radio":
|
|
// No $field->multiple
|
|
$res .= "<div class='form-group";
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " has-".$errors[$field->name][0];
|
|
$res .= "'>\n";
|
|
$res .= " <label class='col-sm-2 control-label' for='".
|
|
$this->formName."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."_0'>";
|
|
$res .= htmlspecialchars ($field->label);
|
|
if (isset ($field->mandatory) && $field->mandatory !== FALSE)
|
|
$res .= " <span style='color:red'>*</span>";
|
|
else
|
|
$res .= " ";
|
|
$res .= "</label>\n";
|
|
$res .= " <div class='col-sm-10'>\n";
|
|
if (is_string ($field->defaults))
|
|
$field->defaults = array ($field->defaults);
|
|
$res .= " <input type='hidden'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."]'";
|
|
$res .= " value='unset'";
|
|
$res .= "/>\n";
|
|
foreach ($field->titles as $key=>$val)
|
|
{
|
|
$res .= " <label class='radio'>";
|
|
$res .= "<input type='radio'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."]'";
|
|
$res .= " id='$this->formName"."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."_$key'";
|
|
$res .= " value='".htmlspecialchars ($val, ENT_QUOTES)."'";
|
|
if (isset ($field->readonly) && $field->readonly !== FALSE)
|
|
$res .= " readonly='readonly'";
|
|
if (isset ($values[$field->name]) &&
|
|
$values[$field->name] === $val)
|
|
$res .= " checked='checked'";
|
|
elseif (isset ($field->defaults[0]) &&
|
|
$field->defaults[0] === $val)
|
|
$res .= " checked='checked'";
|
|
$res .= " class='form-control'";
|
|
$res .= "/>";
|
|
$res .= "$val";
|
|
$res .= "</label>\n"; // End label radio
|
|
}
|
|
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " <span class='help-block'>".$errors[$field->name][1].
|
|
"</span>\n";
|
|
$res .= " </div>\n"; // End controls
|
|
$res .= " </div>\n"; // End form-group
|
|
break;
|
|
|
|
case "select":
|
|
// $values->$field
|
|
$res .= "<div class='form-group";
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " has-".$errors[$field->name][0];
|
|
$res .= "'>\n";
|
|
$res .= " <label class='col-sm-2 control-label' for='".
|
|
$this->formName."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."'>";
|
|
$res .= htmlspecialchars ($field->label);
|
|
if (isset ($field->mandatory) && $field->mandatory !== FALSE)
|
|
$res .= " <span style='color:red'>*</span>";
|
|
else
|
|
$res .= " ";
|
|
$res .= "</label>\n";
|
|
$res .= " <div class='col-sm-10'>\n";
|
|
if (isset ($field->defaults) && is_array ($field->defaults))
|
|
{
|
|
if (isset ($field->readonly) && $field->readonly !== FALSE)
|
|
{
|
|
foreach ($field->defaults as $key=>$val)
|
|
{
|
|
$res .= "<input type='hidden'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."][".
|
|
htmlspecialchars ($key, ENT_QUOTES)."]'";
|
|
$res .= " value='";
|
|
$res .= htmlspecialchars ($val, ENT_QUOTES)."'";
|
|
$res .= "/>";
|
|
}
|
|
}
|
|
|
|
$res .= " <select";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."]";
|
|
if (isset ($field->multiple) && $field->multiple !== FALSE)
|
|
$res .= "[]";
|
|
$res .= "'";
|
|
$res .= " id='$this->formName"."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."'";
|
|
if (isset ($field->multiple) && $field->multiple !== FALSE)
|
|
$res .= " multiple='multiple'";
|
|
if (isset ($field->readonly) && $field->readonly !== FALSE)
|
|
$res .= " disabled='disabled'";
|
|
$res .= " class='form-control'";
|
|
$res .= ">\n";
|
|
foreach ($field->defaults as $key=>$val)
|
|
{
|
|
$res .= " <option value='";
|
|
$res .= htmlspecialchars ($key, ENT_QUOTES)."'";
|
|
if (isset ($values[$field->name]) &&
|
|
is_string ($values[$field->name]) &&
|
|
$values[$field->name] == $key)
|
|
$res .= " selected='selected'";
|
|
elseif (isset ($values[$field->name]) &&
|
|
is_array ($values[$field->name]) &&
|
|
in_array ($key, $values[$field->name]))
|
|
$res .= " selected='selected'";
|
|
$res .= ">";
|
|
$res .= htmlspecialchars ($val);
|
|
$res .= "</option>\n";
|
|
}
|
|
|
|
$res .= " </select>\n";
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " <span class='help-block'>".$errors[$field->name][1].
|
|
"</span>\n";
|
|
}
|
|
else
|
|
{
|
|
$res .= _("No value provided");
|
|
}
|
|
|
|
$res .= " </div>\n"; // End controls
|
|
$res .= " </div>\n"; // End form-group
|
|
break;
|
|
|
|
case "submit":
|
|
// No $field->label, $field->multiple, $field->error
|
|
$res .= "<input type='submit'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."]'";
|
|
$res .= " id='$this->formName"."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."'";
|
|
if (isset ($field->readonly) && $field->readonly !== FALSE)
|
|
$res .= " disabled='disabled'";
|
|
if (isset ($field->defaults))
|
|
$res .= " value='".htmlspecialchars ($field->defaults, ENT_QUOTES).
|
|
"'";
|
|
$res .= " class='form-control btn-primary'";
|
|
$res .= "/>\n";
|
|
break;
|
|
|
|
default:
|
|
// No $field->multiple, $field->titles
|
|
$res .= "<div class='form-group";
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " has-".$errors[$field->name][0];
|
|
$res .= "'>\n";
|
|
$res .= " <label class='col-sm-2 control-label' for='".
|
|
$this->formName."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."'>";
|
|
$res .= htmlspecialchars ($field->label);
|
|
if (isset ($field->mandatory) && $field->mandatory !== FALSE)
|
|
$res .= " <span style='color:red'>*</span>";
|
|
else
|
|
$res .= " ";
|
|
$res .= "</label>\n";
|
|
$res .= " <div class='col-sm-10'>\n";
|
|
$res .= " <input type='text'";
|
|
$res .= " name='$this->formName"."[".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."]'";
|
|
$res .= " id='$this->formName"."_".
|
|
htmlspecialchars ($field->name, ENT_QUOTES)."'";
|
|
if (isset ($values[$field->name]))
|
|
$res .= " value='".htmlspecialchars ($values[$field->name],
|
|
ENT_QUOTES)."'";
|
|
else
|
|
$res .= " value='".htmlspecialchars ($field->defaults, ENT_QUOTES).
|
|
"'";
|
|
if (isset ($field->readonly) && $field->readonly !== FALSE)
|
|
$res .= " readonly='readonly'";
|
|
$res .= " class='form-control'";
|
|
$res .= "/>\n";
|
|
if (isset ($errors[$field->name]))
|
|
$res .= " <span class='help-block'>".$errors[$field->name][1].
|
|
"</span>\n";
|
|
$res .= " </div>\n"; // End controls
|
|
$res .= " </div>\n"; // End form-group
|
|
break;
|
|
}
|
|
|
|
if (isset ($field->group) && $field->group !== $group ||
|
|
!isset ($field->group) && $group !== "")
|
|
{
|
|
$res .="</fieldset>\n";
|
|
$group = "";
|
|
}
|
|
}
|
|
|
|
if ($this->csrf === TRUE)
|
|
{
|
|
$csrf = new csrf ();
|
|
$csrf->field = $this->formName."[".$this->csrfField."]";
|
|
$res .= $csrf->displayFormCSRF ();
|
|
}
|
|
|
|
$res .= "</form>\n";
|
|
return $res;
|
|
}
|
|
|
|
/** Check the token from the user */
|
|
public function checkToken ($tokenFromUser)
|
|
{
|
|
$csrf = new csrf ();
|
|
$csrf->field = $this->csrfField;
|
|
$csrf->checkToken ($tokenFromUser);
|
|
}
|
|
}
|
|
|
|
/** the definition of a formfield */
|
|
class formfield
|
|
{
|
|
/** The name of the field */
|
|
public $name;
|
|
/** The label of the field */
|
|
public $label;
|
|
/** The titles of the field */
|
|
public $titles;
|
|
/** The defaults values of the field */
|
|
public $defaults;
|
|
/** The type of the field (text, password, checkbox, select)*/
|
|
public $type;
|
|
/** The multiplicity of selection of the field (available in select only)*/
|
|
public $multiple;
|
|
/** The name of group for the fields */
|
|
public $group;
|
|
/** The read-only feature of the field */
|
|
public $readonly;
|
|
/** The fonction used to verify the field by AJAX before submitting and
|
|
by PHP after submission*/
|
|
public $verify;
|
|
/** The statut of error of the field */
|
|
public $error;
|
|
/** When adding a field, the name and the label are the minimum mandatory
|
|
@param string $name Name of the field
|
|
@param string $label Label of the field */
|
|
function __construct ($name, $label)
|
|
{
|
|
$this->name = $name;
|
|
$this->label = $label;
|
|
}
|
|
}
|
|
|
|
class csrf
|
|
{
|
|
/** CSRF protection
|
|
By default, the CSRF protection is active if a SESSION is active too.
|
|
It can be disabled if needed. An Exception is raised if the form is send
|
|
back without the token */
|
|
public $csrf=TRUE;
|
|
/** This hidden field name in HTML */
|
|
public $field = "CSRF_TOKEN";
|
|
/** The created token */
|
|
private $csrfToken = "";
|
|
|
|
/** This function return the token */
|
|
public function createToken ()
|
|
{
|
|
$l = 30; // Number of chars in token
|
|
$c = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
for ($s = '',
|
|
$cl = strlen($c)-1, $i = 0; $i < $l; $s .= $c[mt_rand(0, $cl)],
|
|
++$i);
|
|
$this->csrfToken = $s;
|
|
$_SESSION["domframework"]["form"]["csrf"] = $this->csrfToken;
|
|
}
|
|
|
|
/** Check if the provided token is the right token, defined last displayed
|
|
page */
|
|
public function checkToken ($tokenFromUser)
|
|
{
|
|
if ($this->csrf === FALSE )
|
|
return TRUE;
|
|
if (! isset ($_SESSION["domframework"]["form"]["csrf"]))
|
|
throw new Exception (_("No previous CSRF token : abort"));
|
|
if ($_SESSION["domframework"]["form"]["csrf"] !== $tokenFromUser)
|
|
throw new Exception (_("Invalid CSRF token provided"));
|
|
return TRUE;
|
|
}
|
|
|
|
/** Return the CSRF token in a hidden field */
|
|
public function displayFormCSRF ()
|
|
{
|
|
if ($this->csrfToken == "")
|
|
$this->createToken ();
|
|
$res = "<input type='hidden' name='$this->field' ";
|
|
$res .= "value='$this->csrfToken'/>\n";
|
|
return $res;
|
|
}
|
|
}
|
|
|