Skip to content

fix: Handle null byte when validating date/time formats - #939

Merged
DannyvdSluijs merged 4 commits into
mainfrom
fix/null-byte-with-format-date-time
Aug 21, 2026
Merged

fix: Handle null byte when validating date/time formats#939
DannyvdSluijs merged 4 commits into
mainfrom
fix/null-byte-with-format-date-time

Conversation

@DannyvdSluijs

Copy link
Copy Markdown
Collaborator

Description

This PR will add handling of the ValueError thrown when creating DateTime with a null byte (\x00)

Related Issue

Closes #937

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Other (please describe):

Checklist

  • I have read the CONTRIBUTING guidelines
  • My code follows the code style of this project
  • I have added tests that prove my fix is effective or that my feature works
  • All new and existing tests pass
  • I have updated the documentation accordingly
  • My changes generate no new warnings

Additional Notes

@DannyvdSluijs
DannyvdSluijs force-pushed the fix/null-byte-with-format-date-time branch from 7096e37 to e0cda49 Compare August 21, 2026 09:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes date/time format validation for null-byte inputs by handling parser exceptions across legacy and draft validators.

Changes:

  • Handles date/time parsing failures.
  • Adds regression cases across supported drafts.
  • Updates the PHPStan baseline for the new return type.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Review summary
tests/Constraints/FormatConstraintTest.php NUL-byte test input is incorrectly single-quoted and does not exercise the failure path.
tests/Constraints/Draft2019/FormatConstraintTest.php NUL-byte test inputs are incorrectly single-quoted.
tests/Constraints/Draft07/FormatConstraintTest.php NUL-byte test inputs are incorrectly single-quoted.
tests/Constraints/Draft06/FormatConstraintTest.php NUL-byte test inputs are incorrectly single-quoted.
src/JsonSchema/Constraints/FormatConstraint.php Adding : bool creates a backwards-incompatible protected method signature change.
src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php Updates Draft 2019 date/time validation.
src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php Updates Draft 07 date/time validation.
src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php Updates Draft 06 date/time validation.
phpstan-baseline.neon Updates the baseline for the return-type change.
Suppressed comments (5)

src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php:131

  • Catching Throwable here turns any unrelated Error or exception from DateTime::createFromFormat() into an ordinary format failure, which can hide runtime/programming defects. The intended PHP 8 failure is specifically the null-byte case; reject \0 (or otherwise rethrow unexpected throwables) and preserve other failures.
        } catch (\Throwable $e) {
            return false;

src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php:155

  • Catching Throwable here turns any unrelated Error or exception from DateTimeImmutable::createFromFormat() into an ordinary format failure, which can hide runtime/programming defects. The intended PHP 8 failure is specifically the null-byte case; reject \0 (or otherwise rethrow unexpected throwables) and preserve other failures.
        } catch (\Throwable $e) {
            return false;

src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php:146

  • Catching Throwable here turns any unrelated Error or exception from DateTimeImmutable::createFromFormat() into an ordinary format failure, which can hide runtime/programming defects. The intended PHP 8 failure is specifically the null-byte case; reject \0 (or otherwise rethrow unexpected throwables) and preserve other failures.
        } catch (\Throwable $e) {
            return false;

src/JsonSchema/Constraints/FormatConstraint.php:161

  • Catching Throwable here turns any unrelated Error or exception from DateTime::createFromFormat() into an ordinary format failure, which can hide runtime/programming defects. The intended PHP 8 failure is specifically the null-byte case; since this package supports PHP 7.2, reject \0 (or otherwise rethrow unexpected throwables) and preserve other failures.
        } catch (Throwable $e) {
            return false;

tests/Constraints/FormatConstraintTest.php:199

  • This is a single-quoted PHP string, so \x00 remains the literal characters backslash, x, 0, 0; it does not contain a NUL byte. As a result, this case passes even before the new exception handling and does not reproduce the reported failure. Use a double-quoted string (or concatenate "\x00") here.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/JsonSchema/Constraints/FormatConstraint.php Outdated
Comment thread tests/Constraints/Draft06/FormatConstraintTest.php Outdated
Comment thread tests/Constraints/Draft07/FormatConstraintTest.php Outdated
Comment thread tests/Constraints/Draft2019/FormatConstraintTest.php Outdated
@DannyvdSluijs
DannyvdSluijs force-pushed the fix/null-byte-with-format-date-time branch from 9c3a15a to 3e85f69 Compare August 21, 2026 09:50
@DannyvdSluijs
DannyvdSluijs requested a lite review from Copilot August 21, 2026 09:52
@DannyvdSluijs DannyvdSluijs self-assigned this Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (13)

tests/Constraints/Draft06/FormatConstraintTest.php:5

  • Please keep this test in the project's test namespace. Existing tests use JsonSchema\Tests\Constraints... (for example, tests/Constraints/DefaultPropertiesTest.php:5), and autoload-dev only maps the JsonSchema\Tests\ prefix; Constraints\Draft06 leaves this class outside the configured test namespace.
namespace Constraints\Draft06;

tests/Constraints/Draft07/FormatConstraintTest.php:28

  • This provider key says date-time, but the format argument is 'date'. The date-time path uses validateRfc3339DateTime() rather than the changed date/time helper, so this case does not test the format it advertises and can give a false impression of coverage. Rename the case to describe the date format (or change the argument if a separate date-time test is intended).
        yield 'Date-time format with value containing null byte' => ["2020-01-01T12:34:56\x00", 'date'];

tests/Constraints/Draft07/FormatConstraintTest.php:5

  • Please keep this test in the project's test namespace. Existing tests use JsonSchema\Tests\Constraints... (for example, tests/Constraints/DefaultPropertiesTest.php:5), and autoload-dev only maps the JsonSchema\Tests\ prefix; Constraints\Draft07 leaves this class outside the configured test namespace.
namespace Constraints\Draft07;

tests/Constraints/Draft2019/FormatConstraintTest.php:28

  • This provider key says date-time, but the format argument is 'date'. The date-time path uses validateRfc3339DateTime() rather than the changed date/time helper, so this case does not test the format it advertises and can give a false impression of coverage. Rename the case to describe the date format (or change the argument if a separate date-time test is intended).
        yield 'Date-time format with value containing null byte' => ["2020-01-01T12:34:56\x00", 'date'];

tests/Constraints/Draft2019/FormatConstraintTest.php:5

  • Please keep this test in the project's test namespace. Existing tests use JsonSchema\Tests\Constraints... (for example, tests/Constraints/DefaultPropertiesTest.php:5), and autoload-dev only maps the JsonSchema\Tests\ prefix; Constraints\Draft2019 leaves this class outside the configured test namespace.
namespace Constraints\Draft2019;

tests/Constraints/FormatConstraintTest.php:204

  • utc-millisec also routes through this date-time helper (the U format), but the new regression cases cover only date and time. Add a NUL-byte case for utc-millisec so this affected format is protected as well.
    src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php:132
  • Please avoid catching Throwable here. This helper only needs to turn the NUL-related ValueError into a validation failure; catching every Throwable also hides unrelated TypeError/Error failures from DateTime::createFromFormat and makes them look like invalid input. A strpos($datetime, "\\0") guard before the call preserves the other failures.
        try {
            $dt = \DateTime::createFromFormat($format, $datetime);
        } catch (\Throwable $e) {
            return false;
        }

src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php:156

  • Please avoid catching Throwable here. This helper only needs to turn the NUL-related ValueError into a validation failure; catching every Throwable also hides unrelated TypeError/Error failures from DateTimeImmutable::createFromFormat and makes them look like invalid input. A strpos($datetime, "\\0") guard before the call preserves the other failures.
        try {
            $dt = \DateTimeImmutable::createFromFormat($format, $input);
        } catch (\Throwable $e) {
            return false;
        }

src/JsonSchema/Constraints/Drafts/Draft2019/FormatConstraint.php:147

  • Please avoid catching Throwable here. This helper only needs to turn the NUL-related ValueError into a validation failure; catching every Throwable also hides unrelated TypeError/Error failures from DateTimeImmutable::createFromFormat and makes them look like invalid input. A strpos($datetime, "\\0") guard before the call preserves the other failures.
        try {
            $dt = \DateTimeImmutable::createFromFormat($format, $input);
        } catch (\Throwable $e) {
            return false;
        }

src/JsonSchema/Constraints/FormatConstraint.php:162

  • Please avoid catching Throwable here. The reported failure is the ValueError for a NUL argument, but this also swallows TypeError and other Error instances from DateTime::createFromFormat, silently converting internal/runtime failures into ordinary format violations. Reject the NUL before the call (using strpos for this package's PHP 7.2 support) or catch only the expected exception.
        try {
            $dt = \DateTime::createFromFormat($format, (string) $datetime);
        } catch (Throwable $e) {
            return false;
        }

tests/Constraints/Draft06/FormatConstraintTest.php:28

  • This provider key says date-time, but the format argument is 'date'. The date-time path uses validateRfc3339DateTime() rather than the changed date/time helper, so this case does not test the format it advertises and can give a false impression of coverage. Rename the case to describe the date format (or change the argument if a separate date-time test is intended).
        yield 'Date-time format with value containing null byte' => ["2020-01-01T12:34:56\x00", 'date'];

tests/Constraints/Draft07/FormatConstraintTest.php:32

  • utc-millisec also routes through this date-time helper (the U format), but the new regression cases cover only date and time. Add a NUL-byte case for utc-millisec so this affected format is protected as well.
        yield 'Time format with value containing null byte' => ["13:37:00\x00", 'time'];

tests/Constraints/Draft2019/FormatConstraintTest.php:32

  • utc-millisec also routes through this date-time helper (the U format), but the new regression cases cover only date and time. Add a NUL-byte case for utc-millisec so this affected format is protected as well.
        yield 'Time format with value containing null byte' => ["13:37:00\x00", 'time'];

@DannyvdSluijs
DannyvdSluijs force-pushed the fix/null-byte-with-format-date-time branch from 3e85f69 to bf139b0 Compare August 21, 2026 10:04
@DannyvdSluijs
DannyvdSluijs merged commit 7bdba16 into main Aug 21, 2026
18 checks passed
@DannyvdSluijs
DannyvdSluijs deleted the fix/null-byte-with-format-date-time branch August 21, 2026 10:09
github-actions Bot added a commit that referenced this pull request Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Uncaught ValueError from format: date / format: time when the instance contains a NUL byte

2 participants