Fixes for CSRF protection and FormHandler (including calling CSRF validation in FormHandler by default), Switch_/Case_ statements for FormHandler, custom FormHandler error messages for standard validators

feature/formhandler
Sven Slootweg 10 years ago
parent 4ffa38e442
commit 7076e8d0fa

@ -101,3 +101,101 @@ if(!empty($cphp_config->autoloader))
spl_autoload_register('cphp_autoload_class');
}
set_exception_handler(function($e){
/* Intentionally not using the templater here; any inner exceptions
* cause serious debugging issues. Avoiding potential issues by just
* hardcoding the response here, with no code that could raise an
* exception. */
$exception_class = get_class($e);
$exception_message = $e->getMessage();
$exception_file = $e->getFile();
$exception_line = $e->getLine();
$exception_trace = $e->getTraceAsString();
error_log("Uncaught {$exception_class} in {$exception_file}:{$exception_line} ({$exception_message}). Traceback: {$exception_trace}");
switch(strtolower(ini_get('display_errors')))
{
case "1":
case "on":
case "true":
$error_body = "
<p>
An uncaught <span class='detail'>{$exception_class}</span> was thrown, in <span class='detail'>{$exception_file}</span> on line <span class='detail'>{$exception_line}</span>.
</p>
<p>
<span class='message'>{$exception_message}</span>
</p>
<pre>{$exception_trace}</pre>
<p><strong>Important:</strong> These errors should never be displayed on a production server! Make sure that <em>display_errors</em> is turned off in your PHP configuration, if you want to hide these tracebacks.</p>
";
break;
default:
$error_body = "
<p>
Something went wrong while creating this page, but we're not yet quite sure what it was.
</p>
<p>
If the issue persists, please contact the administrator for this application or website.
</p>
";
break;
}
echo("
<!doctype html>
<html>
<head>
<title>An unexpected error occurred.</title>
<style>
body
{
margin: 24px auto;
padding: 24px 16px;
font-family: sans-serif;
font-size: 18px;
width: 960px;
color: #676767;
}
h1
{
border-bottom: 2px solid black;
color: #444444;
font-size: 26px;
padding-bottom: 6px;
}
pre
{
overflow: auto;
font-size: 13px;
color: black;
padding: 10px;
border: 1px solid gray;
border-radius: 6px;
background-color: #F8F8F8;
}
.message
{
font-weight: bold;
color: #5B0000;
}
.detail
{
color: black;
}
</style>
</head>
<body>
<h1>An unexpected error occurred.</h1>
{$error_body}
</body>
</html>
");
die();
});

@ -47,12 +47,17 @@ class CSRF
return preg_replace_callback("/<form[^>]*>(?!\s*<input name=\"_CPHP_CSRF)/i", "CSRF::GenerateReplacement", $input);
}
public static function VerifyToken()
public static function VerifyToken($source = null)
{
if(!empty($_POST['_CPHP_CSRF_KEY']) && !empty($_POST['_CPHP_CSRF_TOKEN']))
if($source == null)
{
$key = $_POST['_CPHP_CSRF_KEY'];
$token = $_POST['_CPHP_CSRF_TOKEN'];
$source = $_POST;
}
if(!empty($source['_CPHP_CSRF_KEY']) && !empty($source['_CPHP_CSRF_TOKEN']))
{
$key = $source['_CPHP_CSRF_KEY'];
$token = $source['_CPHP_CSRF_TOKEN'];
if(empty($_SESSION['_CPHP_CSRF_KEYS'][$key]) || $_SESSION['_CPHP_CSRF_KEYS'][$key] != $token)
{

@ -164,6 +164,20 @@ class CPHPFormValidatorPromiseBaseClass
return $this->next;
}
public function Switch_($varname, $error_message)
{
$this->next = new CPHPFormValidatorOperatorSwitch($this, $varname, $error_message, array_slice(func_get_args(), 2));
$this->next->handler = $this->handler;
return $this->next;
}
public function Case_($value)
{
$this->next = new CPHPFormValidatorOperatorCase($this, $value, array_slice(func_get_args(), 1));
$this->next->handler = $this->handler;
return $this->next;
}
/* Special instructions */
public function AbortIfErrors()
@ -214,6 +228,15 @@ class CPHPFormValidatorPromiseBaseClass
return $this->next;
}
public function ValidateNumeric($key, $critical = false)
{
$this->next = new CPHPFormValidatorPromise($this, $this->handler, $key, array(), "numeric", "The value is not numeric.", $critical, function($key, $value, $args, $handler){
return is_numeric($value) !== false;
});
$this->next->handler = $this->handler;
return $this->next;
}
public function ValidateUrl($key, $critical = false)
{
$this->next = new CPHPFormValidatorPromise($this, $this->handler, $key, array(), "url", "The value is not a valid URL.", $critical, function($key, $value, $args, $handler){
@ -277,6 +300,22 @@ class CPHPFormValidatorPromiseBaseClass
return $this->next;
}
public function ValidateValue($key, $error_message, $values, $critical = false)
{
$this->next = new CPHPFormValidatorPromise($this, $this->handler, $key, array("values" => $values), "value", $error_message, $critical, function($key, $value, $args, $handler){
if(is_array($args["values"]))
{
return in_array($value, $args["values"]);
}
else
{
return ($args["values"] == $value);
}
});
$this->next->handler = $this->handler;
return $this->next;
}
public function ValidateCustom($key, $error_message, $validator, $critical = false)
{
$this->next = new CPHPFormValidatorPromise($this, $this->handler, $key, array(), "custom", $error_message, $critical, $validator);
@ -364,14 +403,16 @@ class CPHPFormValidatorAbortIfErrors extends CPHPFormValidatorPromiseBaseClass
$this->handler = $handler;
}
public function Resolve($results)
public function Resolve($exceptions)
{
if(count($results) > 0)
if(count($exceptions) > 0)
{
throw new FormValidationException("One or more validation errors before an AbortIfErrors statement.", $results);
throw new FormValidationException("One or more validation errors before an AbortIfErrors statement.", $exceptions);
}
else
{
return null;
}
return $results;
}
}
@ -445,6 +486,95 @@ class CPHPFormValidatorOperatorAll extends CPHPFormValidatorOperator
}
}
class CPHPFormValidatorOperatorSwitch extends CPHPFormValidatorOperator
{
/* The 'case' operator has a different constructor; it needs to accept both
* an error message, and a variable to check. */
public function __construct($creator, $varname, $error_message, $children)
{
$this->varname = $varname;
parent::__construct($creator, $error_message, $children);
}
public function Resolve($results)
{
$exceptions = array();
foreach($this->children as $child)
{
/* We have to set the variable name in the child here... only at
* runtime can we establish the link between parent and child. */
$child->varname = $this->varname;
$result = $child->Resolve($exceptions);
if(is_null($result) === false)
{
$exceptions[] = $result;
}
}
if(count($exceptions) > 0)
{
return array(array(
"type" => "operator",
"error_type" => "switch",
"error_msg" => $this->error_message,
"children" => $exceptions
));
}
else
{
return null;
}
}
}
class CPHPFormValidatorOperatorCase extends CPHPFormValidatorOperator
{
/* The 'case' operator has a different constructor; instead of an error message,
* it is passed a "trigger value"; that is, the value on which it will execute. */
public function __construct($creator, $value, $children)
{
/* FIXME: Check if the parent really is a Switch operator... */
/* Grab the variable name to check from the parent. */
$this->value = $value;
parent::__construct($creator, "", $children);
}
public function Resolve($results)
{
if(in_array($this->value, $this->handler->GetValues($this->varname)))
{
$exceptions = array();
foreach($this->children as $child)
{
$result = $child->Resolve($exceptions);
if(is_null($result) === false)
{
$exceptions[] = $result;
}
}
if(count($exceptions) > 0)
{
return array(array( /* TODO: Unpack case errors upon handling? Insignificant wrapper.. */
"type" => "operator",
"error_type" => "case",
"error_msg" => "Errors occurred.",
"children" => $exceptions
));
}
else
{
return null;
}
}
else
{
/* Case didn't trigger; always treat as success in that case. */
return null;
}
}
}
class CPHPFormHandler extends CPHPFormValidatorPromiseBaseClass
{
public function __construct($formdata = null, $no_csrf = false)
@ -463,6 +593,11 @@ class CPHPFormHandler extends CPHPFormValidatorPromiseBaseClass
$this->validation_exceptions = array();
$this->exception_buffer = array();
$this->first_validation = true;
if($no_csrf === false)
{
CSRF::VerifyToken($this->formdata);
}
}
public function StoreValidationException($exception, $validator_object)
@ -544,14 +679,14 @@ class CPHPFormHandler extends CPHPFormValidatorPromiseBaseClass
}
}
public function GetValue($key)
public function GetValue($key, $default=null)
{
/* Returns a single value for the given key. If the key contains an array, it
* will return the first element. If the key does not exist, it will return null. */
if(!isset($this->formdata[$key]))
{
return null;
return $default;
}
elseif(is_array($this->formdata[$key]))
{

Loading…
Cancel
Save