Reject normalized datetime-local inputs

createFromFormat() only returns false on hard parse failures.
Impossible dates such as 2026-02-31 were silently normalized
into March, so a confirmed event could land on a different day
than the booking link supplied. Non-padded values like
2026-2-3T10:00 were also accepted and reformatted.

Parse with a leading "!" so no current-date fields leak in,
require zero warnings and errors from getLastErrors(), and
round-trip the value through the format. Impossible dates,
out-of-range components and non-padded values now throw in
both the confirmation and the creation stage.
This commit is contained in:
stefan.tollkuehn committed 2026-10-26 13:39:07 +01:00
1 parent a9b7fd11af
commit 2b3e96e50a
2 files changed
+53 -15

No files matched your search

+24 -15
View File
@@ -844,27 +844,36 @@ class CalendarController extends Controller
{
$timeZone = $this->dateTimeZone->getTimeZone();
$date = \DateTimeImmutable::createFromFormat(
self::DATETIME_LOCAL,
$value,
$timeZone
);
if ($date === false) {
/*
* "!" resets all fields, so no parts of the current date
* leak in. PHP normalizes impossible dates instead of
* failing (2026-02-31 rolls into March), so require zero
* warnings and an exact format round-trip on top of the
* parse result.
*/
foreach (['!' . self::DATETIME_LOCAL, '!Y-m-d\TH:i:s'] as $format) {
$date = \DateTimeImmutable::createFromFormat(
'Y-m-d\TH:i:s',
$format,
$value,
$timeZone
);
$errors = \DateTimeImmutable::getLastErrors();
if (
$date !== false
&& ($errors === false
|| ($errors['warning_count'] === 0
&& $errors['error_count'] === 0))
&& $date->format(substr($format, 1)) === $value
) {
return $date;
}
}
if ($date === false) {
throw new \InvalidArgumentException(
$this->l10n->t('Invalid date/time.')
);
}
return $date;
throw new \InvalidArgumentException(
$this->l10n->t('Invalid date/time.')
);
}
/**