From c4f04e5fdd24f82b7630ac127375c89dabf50016 Mon Sep 17 00:00:00 2001 From: Joe Ferguson Date: Tue, 14 Jul 2026 11:25:29 -0500 Subject: [PATCH] Fix submit-event.php crash on non-numeric date fields The event submission form passed $_POST date/recurrence fields straight to checkdate() and mktime(). Under PHP 8 these require int, so any non-numeric string or array value (bots and scanners fuzzing the form) threw an uncaught TypeError -- 9,393 fatals in a 15-day window. Normalize the numeric fields to integers at the input boundary via a new testable phpweb\Events\EventInput::normalizeNumericFields(); non-numeric input becomes 0, an invalid date the existing validation already rejects. Add unit tests for the normalization and a regression test that the normalized values are safe to pass to checkdate(). --- src/Events/EventInput.php | 38 +++++++++++++++ submit-event.php | 15 +++--- tests/Unit/Events/EventInputTest.php | 72 ++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 src/Events/EventInput.php create mode 100644 tests/Unit/Events/EventInputTest.php diff --git a/src/Events/EventInput.php b/src/Events/EventInput.php new file mode 100644 index 0000000000..44d2260f64 --- /dev/null +++ b/src/Events/EventInput.php @@ -0,0 +1,38 @@ + $post + * @return array + */ + public static function normalizeNumericFields(array $post): array + { + foreach (self::NUMERIC_FIELDS as $field) { + $value = $post[$field] ?? null; + $post[$field] = is_numeric($value) ? (int) $value : 0; + } + + return $post; + } +} diff --git a/submit-event.php b/submit-event.php index 2d2ac20044..4809ed9b6e 100644 --- a/submit-event.php +++ b/submit-event.php @@ -1,4 +1,6 @@ '12', 'sday' => '25', 'syear' => '2026']); + + self::assertSame(12, $result['smonth']); + self::assertSame(25, $result['sday']); + self::assertSame(2026, $result['syear']); + } + + public function testCoercesNonNumericStringsToZero(): void + { + $result = EventInput::normalizeNumericFields(['smonth' => 'January', 'sday' => 'abc', 'syear' => '12abc']); + + self::assertSame(0, $result['smonth']); + self::assertSame(0, $result['sday']); + self::assertSame(0, $result['syear']); + } + + public function testCoercesArrayValuesToZero(): void + { + $result = EventInput::normalizeNumericFields(['smonth' => ['x'], 'sday' => [], 'syear' => ['1', '2']]); + + self::assertSame(0, $result['smonth']); + self::assertSame(0, $result['sday']); + self::assertSame(0, $result['syear']); + } + + public function testDefaultsMissingFieldsToZero(): void + { + $result = EventInput::normalizeNumericFields([]); + + foreach (['sday', 'smonth', 'syear', 'eday', 'emonth', 'eyear', 'recur', 'recur_day'] as $field) { + self::assertSame(0, $result[$field], $field); + } + } + + public function testLeavesNonNumericFieldsUntouched(): void + { + $result = EventInput::normalizeNumericFields(['email' => 'a@b.com', 'type' => 'multi', 'smonth' => '5']); + + self::assertSame('a@b.com', $result['email']); + self::assertSame('multi', $result['type']); + self::assertSame(5, $result['smonth']); + } + + /** + * Regression test for the production fatal: + * Uncaught TypeError: checkdate(): Argument #1 ($month) must be of type int, string given + * + * After normalization the date parts are always integers, so passing them to + * checkdate() (and mktime()) can no longer throw a TypeError; garbage input + * simply becomes an invalid (0) date that the normal validation rejects. + */ + public function testNormalizedValuesAreSafeForCheckdate(): void + { + $result = EventInput::normalizeNumericFields(['smonth' => 'notamonth', 'sday' => '1', 'syear' => '2026']); + + self::assertFalse(checkdate($result['smonth'], $result['sday'], $result['syear'])); + } +}