From 98962d9b9e68d41c3aff3872c4ead718b2f41a15 Mon Sep 17 00:00:00 2001 From: W0rma Date: Sat, 11 Jan 2025 15:40:54 +0100 Subject: [PATCH] fix: Succeed format validation if the type is not in the set of given instance types (#773) See https://json-schema.org/draft-04/draft-fge-json-schema-validation-00#rfc.section.7.1: If the type of the instance to validate is not in this set, validation for this format attribute and instance SHOULD succeed. --- CHANGELOG.md | 1 + .../Constraints/FormatConstraint.php | 28 +++++++++++++----- tests/Constraints/FormatTest.php | 29 +++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2736caa..5b519bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix wrong combined paths when traversing upward, fixes #557 ([#652](https://github.com/jsonrainbow/json-schema/pull/652)) - Correct PHPStan baseline ([#764](https://github.com/jsonrainbow/json-schema/pull/764)) - Correct spacing issue in `README.md` ([#763](https://github.com/jsonrainbow/json-schema/pull/763)) +- Format attribute: do not validate data instances that aren't the instance type to validate ([#773](https://github.com/jsonrainbow/json-schema/pull/773)) ### Changed - Bump to minimum PHP 7.2 ([#746](https://github.com/jsonrainbow/json-schema/pull/746)) diff --git a/src/JsonSchema/Constraints/FormatConstraint.php b/src/JsonSchema/Constraints/FormatConstraint.php index dbc35475..afb27b0f 100644 --- a/src/JsonSchema/Constraints/FormatConstraint.php +++ b/src/JsonSchema/Constraints/FormatConstraint.php @@ -35,7 +35,7 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = switch ($schema->format) { case 'date': - if (!$date = $this->validateDateTime($element, 'Y-m-d')) { + if (is_string($element) && !$date = $this->validateDateTime($element, 'Y-m-d')) { $this->addError(ConstraintError::FORMAT_DATE(), $path, [ 'date' => $element, 'format' => $schema->format @@ -45,7 +45,7 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = break; case 'time': - if (!$this->validateDateTime($element, 'H:i:s')) { + if (is_string($element) && !$this->validateDateTime($element, 'H:i:s')) { $this->addError(ConstraintError::FORMAT_TIME(), $path, [ 'time' => json_encode($element), 'format' => $schema->format, @@ -55,7 +55,7 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = break; case 'date-time': - if (null === Rfc3339::createFromString($element)) { + if (is_string($element) && null === Rfc3339::createFromString($element)) { $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, [ 'dateTime' => json_encode($element), 'format' => $schema->format @@ -101,14 +101,14 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = break; case 'uri': - if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { + if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); } break; case 'uriref': case 'uri-reference': - if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { + if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { // FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them. // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information. @@ -133,20 +133,20 @@ public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = break; case 'email': - if (null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE)) { + if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE)) { $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); } break; case 'ip-address': case 'ipv4': - if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { + if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); } break; case 'ipv6': - if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { + if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); } break; @@ -186,11 +186,19 @@ protected function validateDateTime($datetime, $format) protected function validateRegex($regex) { + if (!is_string($regex)) { + return true; + } + return false !== @preg_match(self::jsonPatternToPhpRegex($regex), ''); } protected function validateColor($color) { + if (!is_string($color)) { + return true; + } + if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'])) { @@ -215,6 +223,10 @@ protected function validatePhone($phone) protected function validateHostname($host) { + if (!is_string($host)) { + return true; + } + $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i'; return preg_match($hostnameRegex, $host); diff --git a/tests/Constraints/FormatTest.php b/tests/Constraints/FormatTest.php index 2d91e5fe..25eab1c0 100644 --- a/tests/Constraints/FormatTest.php +++ b/tests/Constraints/FormatTest.php @@ -100,10 +100,14 @@ public function getValidFormats(): array return [ ['2001-01-23', 'date'], ['2000-02-29', 'date'], + [42, 'date'], + [4.2, 'date'], ['12:22:01', 'time'], ['00:00:00', 'time'], ['23:59:59', 'time'], + [42, 'time'], + [4.2, 'time'], ['2000-05-01T12:12:12Z', 'date-time'], ['2000-05-01T12:12:12+0100', 'date-time'], @@ -114,6 +118,8 @@ public function getValidFormats(): array ['2000-05-01T12:12:12.0Z', 'date-time'], ['2000-05-01T12:12:12.000Z', 'date-time'], ['2000-05-01T12:12:12.000000Z', 'date-time'], + [42, 'date-time'], + [4.2, 'date-time'], ['0', 'utc-millisec'], @@ -136,6 +142,8 @@ public function getValidFormats(): array ['yellow', 'color'], ['#fff', 'color'], ['#00cc00', 'color'], + [42, 'color'], + [4.2, 'color'], ['background: blue', 'style'], ['color: #000;', 'style'], @@ -149,18 +157,39 @@ public function getValidFormats(): array ['./relative:PathReference/', 'uri-reference'], ['relativePathReference/', 'uri-reference'], ['relative/Path:Reference/', 'uri-reference'], + [42, 'uri-reference'], + [4.2, 'uri-reference'], ['info@something.edu', 'email'], + [42, 'email'], + [4.2, 'email'], ['10.10.10.10', 'ip-address'], ['127.0.0.1', 'ip-address'], + [42, 'ip-address'], + [4.2, 'ip-address'], + + ['127.0.0.1', 'ipv4'], + [42, 'ipv4'], + [4.2, 'ipv4'], ['::ff', 'ipv6'], + [42, 'ipv6'], + [4.2, 'ipv6'], ['www.example.com', 'host-name'], ['3v4l.org', 'host-name'], ['a-valid-host.com', 'host-name'], ['localhost', 'host-name'], + [42, 'host-name'], + [4.2, 'host-name'], + + ['www.example.com', 'hostname'], + ['3v4l.org', 'hostname'], + ['a-valid-host.com', 'hostname'], + ['localhost', 'hostname'], + [42, 'hostname'], + [4.2, 'hostname'], ['anything', '*'], ['unknown', '*'],