From 8b2f069480d14684896e16696a4f70e1539e9375 Mon Sep 17 00:00:00 2001 From: david-collibra Date: Wed, 4 Mar 2026 18:15:56 +0100 Subject: [PATCH 1/2] Disable lenient parsing in DateFormType FastDateFormat.parseObject() silently accepted invalid dates by rolling over values (e.g. month 13 became month 1 of the next year). Use SimpleDateFormat with setLenient(false) to reject invalid dates. --- .../engine/impl/form/DateFormType.java | 5 +- .../engine/impl/form/DateFormTypeTest.java | 83 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 modules/flowable-engine/src/test/java/org/flowable/engine/impl/form/DateFormTypeTest.java diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/DateFormType.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/DateFormType.java index 02cae9d3f4e..c891b18a78a 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/DateFormType.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/DateFormType.java @@ -15,6 +15,7 @@ import java.text.Format; import java.text.ParseException; +import java.text.SimpleDateFormat; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.FastDateFormat; @@ -55,7 +56,9 @@ public Object convertFormValueToModelValue(String propertyValue) { return null; } try { - return dateFormat.parseObject(propertyValue); + SimpleDateFormat sdf = new SimpleDateFormat(datePattern); + sdf.setLenient(false); + return sdf.parse(propertyValue); } catch (ParseException e) { throw new FlowableIllegalArgumentException("invalid date value " + propertyValue, e); } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/impl/form/DateFormTypeTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/impl/form/DateFormTypeTest.java new file mode 100644 index 00000000000..f869029d0d0 --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/impl/form/DateFormTypeTest.java @@ -0,0 +1,83 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.flowable.engine.impl.form; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Date; + +import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.junit.jupiter.api.Test; + +class DateFormTypeTest { + + private final DateFormType dateFormType = new DateFormType("dd/MM/yyyy"); + + @Test + void nullValueReturnsNull() { + assertThat(dateFormType.convertFormValueToModelValue(null)).isNull(); + } + + @Test + void emptyValueReturnsNull() { + assertThat(dateFormType.convertFormValueToModelValue("")).isNull(); + } + + @Test + void validDateIsParsed() { + Object result = dateFormType.convertFormValueToModelValue("15/06/2024"); + assertThat(result).isInstanceOf(Date.class); + } + + @Test + void invalidMonthThrowsException() { + assertThatThrownBy(() -> dateFormType.convertFormValueToModelValue("15/13/2024")) + .isInstanceOf(FlowableIllegalArgumentException.class); + } + + @Test + void invalidDayThrowsException() { + assertThatThrownBy(() -> dateFormType.convertFormValueToModelValue("32/06/2024")) + .isInstanceOf(FlowableIllegalArgumentException.class); + } + + @Test + void wrongFormatThrowsException() { + assertThatThrownBy(() -> dateFormType.convertFormValueToModelValue("2024-06-15")) + .isInstanceOf(FlowableIllegalArgumentException.class); + } + + @Test + void differentFormatValidDateIsParsed() { + DateFormType isoFormat = new DateFormType("yyyy-MM-dd"); + Object result = isoFormat.convertFormValueToModelValue("2024-06-15"); + assertThat(result).isInstanceOf(Date.class); + } + + @Test + void differentFormatInvalidDayThrowsException() { + DateFormType isoFormat = new DateFormType("yyyy-MM-dd"); + assertThatThrownBy(() -> isoFormat.convertFormValueToModelValue("2024-06-32")) + .isInstanceOf(FlowableIllegalArgumentException.class); + } + + @Test + void inputValidForOtherFormatThrowsException() { + DateFormType isoFormat = new DateFormType("yyyy-MM-dd"); + assertThatThrownBy(() -> isoFormat.convertFormValueToModelValue("15/06/2024")) + .isInstanceOf(FlowableIllegalArgumentException.class); + } + +} From 577c1beb20972fc481fc692244ad7d3b858468ec Mon Sep 17 00:00:00 2001 From: david-collibra Date: Wed, 18 Mar 2026 13:55:51 +0100 Subject: [PATCH 2/2] Replace FastDateFormat field with local SimpleDateFormat instances FastDateFormat does not support lenient=false. Using local SimpleDateFormat instances in both methods avoids shared state and is thread-safe. The overhead of creating a new instance per call is acceptable as these methods are not expected to be called frequently on the same object. --- .../java/org/flowable/engine/impl/form/DateFormType.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/DateFormType.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/DateFormType.java index c891b18a78a..38aa26795fa 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/DateFormType.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/DateFormType.java @@ -13,12 +13,10 @@ package org.flowable.engine.impl.form; -import java.text.Format; import java.text.ParseException; import java.text.SimpleDateFormat; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.time.FastDateFormat; import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.engine.form.AbstractFormType; @@ -30,11 +28,9 @@ public class DateFormType extends AbstractFormType { private static final long serialVersionUID = 1L; protected String datePattern; - protected Format dateFormat; public DateFormType(String datePattern) { this.datePattern = datePattern; - this.dateFormat = FastDateFormat.getInstance(datePattern); } @Override @@ -69,6 +65,6 @@ public String convertModelValueToFormValue(Object modelValue) { if (modelValue == null) { return null; } - return dateFormat.format(modelValue); + return new SimpleDateFormat(datePattern).format(modelValue); } }