Skip to content

Commit

Permalink
Allow form to be rendered with a "get" method attr
Browse files Browse the repository at this point in the history
Forbid "file" elements when doing so and omit CSRF token.
  • Loading branch information
JRaspass committed Sep 7, 2021
1 parent e92617e commit 8f325e6
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 10 deletions.
24 changes: 21 additions & 3 deletions lib/Cro/WebApp/Form.pm6
Expand Up @@ -247,6 +247,16 @@ class Cro::WebApp::Form::ValidationState {
}
}

#| Thrown when a form containing a file element is rendered with a GET method.
class X::Cro::WebApp::Form::FileInGET is Exception {
has Str $.form is required;
has Str $.element is required;

method message {
"Form '$.form' cannot contain element '$.element' with 'GET' method";
}
}

#| A role to be composed into Cro web application form objects, providing the key form
#| functionality.
role Cro::WebApp::Form {
Expand All @@ -256,6 +266,9 @@ role Cro::WebApp::Form {
#| Cached rendered data, in case it is asked for multiple times.
has $!cached-render-data;

#| The HTTP method for which $!cached-render-data is valid.
has $!cached-render-method;

#| Computed validation state.
has Cro::WebApp::Form::ValidationState $!validation-state;

Expand Down Expand Up @@ -374,8 +387,10 @@ role Cro::WebApp::Form {

#| Produce a description of the form and its content for use in rendering
#| the form to HTML.
method HTML-RENDER-DATA(--> Hash) {
.return with $!cached-render-data;
method HTML-RENDER-DATA(:$method where "get"|"post" = "post" --> Hash) {
return $!cached-render-data
if $!cached-render-data.defined && $!cached-render-method eq $method;

my @controls;
my %validation-by-control;
with $!validation-state {
Expand All @@ -386,6 +401,8 @@ role Cro::WebApp::Form {
for self.^attributes.grep(*.has_accessor) -> Attribute $attr {
my ($control-type, %properties) = self!calculate-control-type($attr);
my $name = $attr.name.substr(2);
die X::Cro::WebApp::Form::FileInGET.new :form(::?CLASS.^name) :element($name)
if $control-type eq 'file' && $method eq 'get';
my %control =
:$name,
label => self!calculate-label($attr),
Expand All @@ -399,12 +416,13 @@ role Cro::WebApp::Form {
}
@controls.push(%control);
}
self!add-csrf-protection(@controls);
self!add-csrf-protection(@controls) if $method eq 'post';
my %enctype = any(self.^attributes.map(*.?webapp-form-type).grep(*.defined)) eq 'file' ?? enctype => "multipart/form-data" !! Empty;
my %rendered := { :@controls, was-validated => $!validation-state.defined, |%enctype };
if %validation-by-control{''} -> @errors {
%rendered<validation-errors> = [@errors.map(*.message)];
}
$!cached-render-method = $method;
return $!cached-render-data := %rendered;
}

Expand Down
14 changes: 7 additions & 7 deletions resources/prelude.crotmp
Expand Up @@ -21,12 +21,12 @@
</?>
</:>

<:sub form-errors($_, :$form-errors-class, :$form-errors-text)>
<?.HTML-RENDER-DATA.<validation-errors>>
<:sub form-errors($_, :$form-errors-class, :$form-errors-text, :$method)>
<?.HTML-RENDER-DATA(:$method).<validation-errors>>
<div<&class($form-errors-class)>>
<?$form-errors-text><$form-errors-text></?>
<ul>
<@HTML-RENDER-DATA.<validation-errors>>
<@HTML-RENDER-DATA(:$method).<validation-errors>>
<li><$_></li>
</@>
</ul>
Expand All @@ -39,10 +39,10 @@
:$novalidate, :$was-validated-class, :$is-invalid-class,
:$invalid-feedback-class, :$form-errors-class, :$form-errors-text,
:$help-class, :$submit-button-class, :$submit-button-text,
:$action, :$name)>
<form name="<?$name><$name></?><!$name><.GENERATE-NAME></!>" method="post"<?$action> action="<$action>"</?><?$novalidate> novalidate</?><?{ .<was-validated> && $was-validated-class }> class="<$was-validated-class>"</?><?.HTML-RENDER-DATA.enctype> enctype="<.HTML-RENDER-DATA.enctype>"</?>>
<&form-errors($_, :$form-errors-class, :$form-errors-text)>
<@HTML-RENDER-DATA.controls>
:$action, :$name, :$method = 'post')>
<form name="<?$name><$name></?><!$name><.GENERATE-NAME></!>" method="<$method>"<?$action> action="<$action>"</?><?$novalidate> novalidate</?><?{ .<was-validated> && $was-validated-class }> class="<$was-validated-class>"</?><?.HTML-RENDER-DATA(:$method).enctype> enctype="<.HTML-RENDER-DATA(:$method).enctype>"</?>>
<&form-errors($_, :$form-errors-class, :$form-errors-text, :$method)>
<@HTML-RENDER-DATA(:$method).controls>
<?{ .type eq 'text' || .type eq 'email' || .type eq 'search' || .type eq 'url' || .type eq 'tel' }>
<div<&class($input-group-class)>>
<label for="<.name>"<&class($input-label-class)>><.label></label>
Expand Down
21 changes: 21 additions & 0 deletions t/form-get.t
@@ -0,0 +1,21 @@
use Cro::WebApp::Form;
use Cro::WebApp::Template::Repository;
use Test;

class Search does Cro::WebApp::Form { has $.query is search }
class Upload does Cro::WebApp::Form { has $.photo is file }

constant template = parse-template <&form($_, :method('get'))>;

with template.render: Search.empty {
like $_, / 'method="get"' /, 'Form method is GET';

unlike $_, / '__CSRF_TOKEN' /, 'CSRF token is omitted';
}

throws-like { template.render: Upload.empty },
X::Cro::WebApp::Form::FileInGET,
:message("Form 'Upload' cannot contain element 'photo' with 'GET' method"),
'File element in a GET form dies';

done-testing;

0 comments on commit 8f325e6

Please sign in to comment.