From 97f1f67af10e0628611a5d29a4bda15890bbd50e Mon Sep 17 00:00:00 2001 From: Dave M Date: Thu, 3 Feb 2022 20:06:04 +0000 Subject: [PATCH] - Implemented CSRF check, some bugfixes also included --- src/Common/UiElement.php | 22 +++++++++++++++++----- src/Component/UiPopup.php | 2 +- src/Form/UiForm.php | 18 +++++++++++++++--- src/Form/UiInput.php | 2 +- src/Form/UiTextarea.php | 23 +++++++++++++++++++++-- src/Method/Form.php | 16 ---------------- src/Method/FormHandler.php | 20 +++++++++++++++++--- 7 files changed, 72 insertions(+), 31 deletions(-) diff --git a/src/Common/UiElement.php b/src/Common/UiElement.php index f130a4c..e941c6d 100644 --- a/src/Common/UiElement.php +++ b/src/Common/UiElement.php @@ -177,8 +177,10 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable { } } else if ( !is_numeric($key) ) { - # will output something like - $attributesList[] = strpos($value, '"') !== false ? "$key='$value'" : "$key=\"$value\""; + $value = htmlspecialchars($value); + + # will output something like + $attributesList[] = "$key=\"$value\""; } else { # will output something like @@ -231,7 +233,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable { public function html($set = null) { if ($set !== null) { $this->content = $set; - + return $this; } @@ -285,8 +287,18 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable { break; case 'style' : - foreach($value as $var => $val) { - $this->css($var, $val); + if ( is_string($value) ) + { + foreach(array_filter(explode(";", $value)) as $vars) { + list($key, $value) = explode(':', $vars); + + $this->css($key, $value); + } + } + elseif ( is_array($value) ) { + foreach($value as $var => $val) { + $this->css($var, $val); + } } break; diff --git a/src/Component/UiPopup.php b/src/Component/UiPopup.php index e7a152f..248652a 100644 --- a/src/Component/UiPopup.php +++ b/src/Component/UiPopup.php @@ -14,7 +14,7 @@ class UiPopup extends UiElement implements Extension { public string $tag = "div"; public array $attributes = [ - 'class' => 'ui-popup', + 'class' => 'ui-popup', ]; public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string diff --git a/src/Form/UiForm.php b/src/Form/UiForm.php index 5d5b5e2..aba53b1 100644 --- a/src/Form/UiForm.php +++ b/src/Form/UiForm.php @@ -54,7 +54,7 @@ class UiForm extends UiElement implements Extension { public function buildHtml(string $method = "get", string $name = "", string $action = "", array $attributes = []) : string { $method = strtolower($method); - + $this->option('tag-type', 'single'); @@ -66,11 +66,23 @@ class UiForm extends UiElement implements Extension { $this->attributes([ 'method' => $method, 'action' => $action ] + $attributes); if ( $method !== "get" ) { - $this->append( ( new UiHidden() )->attributes([ 'name' =>"picea-ui-form[$name]", 'value' => md5( $name . microtime() ) ]) ); + $token = md5( $name . microtime()); + $key = "picea-ui.form:{$name}"; + + if (count($_SESSION[$key] ?? []) > 100) { + array_shift($_SESSION[$key]); + } + + $_SESSION[$key][] = $token; + + $this->append( ( new UiHidden() )->attributes([ + 'name' => "picea-ui-form[$name]", + 'value' => $token, + ])); $this->attributes([ 'enctype' => "multipart/form-data" ]); } - + return $this->render() . PHP_EOL; } } \ No newline at end of file diff --git a/src/Form/UiInput.php b/src/Form/UiInput.php index 78b507b..d3812ff 100644 --- a/src/Form/UiInput.php +++ b/src/Form/UiInput.php @@ -43,7 +43,7 @@ class UiInput extends UiElement implements Extension { } if ($attributes['class'] ?? false) { - $attributes['class'] = implode(" ", array_merge((array) $attributes['class'], (array) $this->attributes['class'])); + $attributes['class'] .= " {$this->attributes['class']}"; unset($this->attributes['class']); } diff --git a/src/Form/UiTextarea.php b/src/Form/UiTextarea.php index 1cfc674..c7c8b42 100644 --- a/src/Form/UiTextarea.php +++ b/src/Form/UiTextarea.php @@ -4,7 +4,7 @@ namespace Picea\Ui\Form; class UiTextarea extends UiInput { - public string $token = "ui.textarea"; + public array $tokens = [ "ui.textarea", "ui.textarea.raw" ]; public string $tag = "textarea"; @@ -14,8 +14,27 @@ class UiTextarea extends UiInput { public array $options = []; + protected bool $echoRaw = false; + + public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string + { + if ($token === 'ui.textarea.raw') { + $this->echoRaw = true; + } + + return sprintf("echoRaw(%s)->buildHtml($arguments) ?>", $this->echoRaw ? 'true' : 'false'); + } + + protected function setValue($value) : void { - $this->html($value); + $this->echoRaw ? $this->html($value) : $this->text($value); + } + + public function echoRaw(bool $set) : self + { + $this->echoRaw = $set; + + return $this; } } diff --git a/src/Method/Form.php b/src/Method/Form.php index 256e0ef..e1796ec 100644 --- a/src/Method/Form.php +++ b/src/Method/Form.php @@ -32,22 +32,6 @@ class Form implements Extension { $context->pushFunction("form", [ $this, 'formClass' ]); } - public function form_csrf(string $field, string $value) { - $values = $this->session("View.form.csrf.$field") ?: []; - - # keeps 20 (from config) latest CSRF key for this form into session, - # allowing more than one tab opened and preventing information loss - if ( count($values) >= 20 ) { - #array_shift($values); - } - - $values[] = $value; - - $this->session("View.form.csrf.$field", $values); - - return $value; - } - public function formClass(FormInterface $form, ? FormContext $formContext = null) : FormHandler { return new FormHandler($this->request, $form, $formContext); diff --git a/src/Method/FormHandler.php b/src/Method/FormHandler.php index ba8bab7..e6210e7 100644 --- a/src/Method/FormHandler.php +++ b/src/Method/FormHandler.php @@ -5,8 +5,11 @@ namespace Picea\Ui\Method; use Psr\Http\Message\ServerRequestInterface; class FormHandler { + public bool $sent = false; + public bool $validateCsrfToken = true; + public ? bool $executionStatus = null; public FormContext $context; @@ -38,7 +41,20 @@ class FormHandler { { if ( false !== $this->context->formSent = $this->sent ) { if ( $this->context->formName ?? false ) { - $this->sent = $this->context->formSent = (bool) ( $this->request->getParsedBody()['picea-ui-form'][$this->context->formName] ?? false ); + $sent = false; + + $token = $this->context->{'picea-ui-form'}[$this->context->formName] ?? false; + + if ( $token ) { + if ($this->validateCsrfToken) { + $sent = in_array($token, $_SESSION["picea-ui.form:{$this->context->formName}"] ?? []); + } + else { + $sent = (bool) $token; + } + } + + $this->sent = $this->context->formSent = $sent; } } } @@ -50,8 +66,6 @@ class FormHandler { if ( $this->sent ) { if ( $this->form->validate($this->context) ) { $this->executionStatus = $this->form->execute($this->context); - - $this->context->formExecuted = true; } } }