Lukasz Lenart created WW-5704:
---------------------------------
Summary: HTML5 required false-rejects on radio/file when the bound
property is never null
Key: WW-5704
URL: https://issues.apache.org/jira/browse/WW-5704
Project: Struts 2
Issue Type: Bug
Reporter: Lukasz Lenart
Fix For: 7.4.0
Found reviewing WW-5695 (PR #1865). Violates that feature's governing rule:
emit a constraint only when the browser cannot reject input the server would
accept.
h3. What happens
{{StrutsHtmlConstraintProvider.addRequiredField}} emits {{required}} for RADIO
and FILE only, and its javadoc explains why:
bq. A control that submits an empty string rather than omitting the parameter
therefore passes server-side while the browser blocks it [...] Only RADIO and
FILE omit the parameter entirely when empty, so only they agree with the
browser.
That reasoning is about the HTTP request. {{RequiredFieldValidator}} never sees
the request - it inspects the already-bound property:
{code:java}
public void validate(Object object) throws ValidationException {
String fieldName = getFieldName();
Object value = this.getFieldValue(fieldName, object);
if (value == null) {
addFieldError(fieldName, object);
} else if (value.getClass().isArray() && Array.getLength(value) == 0) {
addFieldError(fieldName, object);
} else if (Collection.class.isAssignableFrom(value.getClass()) &&
((Collection) value).isEmpty()) {
addFieldError(fieldName, object);
}
}
{code}
For a primitive-typed or otherwise default-initialised property the server can
therefore _never_ fail, while the browser's {{required}} still can.
h3. Reproduction
The action declares {{private int priority;}} with a {{required}}
field-validator on it, rendered as {{s:radio}} with {{name}} of {{priority}}
and a list of 1, 2, 3. On first render nothing is selected, because 0 is not in
the list.
* Browser: no radio in the group is selected, so the submit is blocked.
* Server: {{getFieldValue}} returns {{Integer.valueOf(0)}}, which is not null,
so no error is added and the form would have been accepted.
Same shape for {{s:file}} whenever {{prepare()}} pre-populates the file
property from an existing entity, which is the ordinary edit-an-attachment flow.
h3. Why it is not simply fixable in the provider
There is no render-time way to know a property's default value, so the two
honest options are:
* drop the {{RequiredFieldValidator}} to {{required}} mapping entirely, leaving
only {{requiredstring}}; or
* keep it and document it as a deliberate, narrow deviation from the
never-false-reject rule.
h3. Not affected
The {{requiredstring}} mapping is sound and should stay:
{{RequiredStringValidator}} fails on null, on empty, and by default on blank,
so the browser's {{required}} can only reject what the server would also
reject. That mapping reasons about the value, not the request, and so does not
have this defect.
h3. Test gap
No test renders {{required}} end-to-end on {{s:radio}} or {{s:file}}; only
{{minlength}} is asserted through a template.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)