From 046f6b4431629bfe1520f002c8f01ea347106e39 Mon Sep 17 00:00:00 2001 From: Guilliam Xavier Date: Thu, 4 Feb 2021 13:53:12 +0100 Subject: [PATCH 1/3] Streamline validation of patternProperties Regex And always escape used delimiter in passed pattern. Refs #650 --- src/JsonSchema/Constraints/ObjectConstraint.php | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/JsonSchema/Constraints/ObjectConstraint.php b/src/JsonSchema/Constraints/ObjectConstraint.php index dd1c02b9..768b6793 100644 --- a/src/JsonSchema/Constraints/ObjectConstraint.php +++ b/src/JsonSchema/Constraints/ObjectConstraint.php @@ -54,24 +54,17 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $prop public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties) { - $try = array('/', '#', '+', '~', '%'); $matches = array(); foreach ($patternProperties as $pregex => $schema) { - $delimiter = '/'; - // Choose delimiter. Necessary for patterns like ^/ , otherwise you get error - foreach ($try as $delimiter) { - if (strpos($pregex, $delimiter) === false) { // safe to use - break; - } - } + $fullRegex = '#' . str_replace('#', '\\#', $pregex) . '#u'; // Validate the pattern before using it to test for matches - if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) { + if (@preg_match($fullRegex, '') === false) { $this->addError(ConstraintError::PREGEX_INVALID(), $path, array('pregex' => $pregex)); continue; } foreach ($element as $i => $value) { - if (preg_match($delimiter . $pregex . $delimiter . 'u', $i)) { + if (preg_match($fullRegex, $i)) { $matches[] = $i; $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults)); } From 3d4469caadb845e439ae3e7ba3a0b3c0ccd73011 Mon Sep 17 00:00:00 2001 From: Guilliam Xavier Date: Thu, 4 Feb 2021 15:35:30 +0100 Subject: [PATCH 2/3] Factorize pattern-to-regex --- src/JsonSchema/Constraints/BaseConstraint.php | 12 ++++++++++++ src/JsonSchema/Constraints/FormatConstraint.php | 2 +- src/JsonSchema/Constraints/ObjectConstraint.php | 2 +- src/JsonSchema/Constraints/StringConstraint.php | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/JsonSchema/Constraints/BaseConstraint.php b/src/JsonSchema/Constraints/BaseConstraint.php index a0d1d8b8..43498ee3 100644 --- a/src/JsonSchema/Constraints/BaseConstraint.php +++ b/src/JsonSchema/Constraints/BaseConstraint.php @@ -153,4 +153,16 @@ public static function arrayToObjectRecursive($array) return (object) json_decode($json); } + + /** + * Transform a JSON pattern into a PCRE regex + * + * @param string $pattern + * + * @return string + */ + public static function jsonPatternToPhpRegex($pattern) + { + return '#' . str_replace('#', '\\#', $pattern) . '#u'; + } } diff --git a/src/JsonSchema/Constraints/FormatConstraint.php b/src/JsonSchema/Constraints/FormatConstraint.php index a55356d1..2620e7d6 100644 --- a/src/JsonSchema/Constraints/FormatConstraint.php +++ b/src/JsonSchema/Constraints/FormatConstraint.php @@ -197,7 +197,7 @@ protected function validateDateTime($datetime, $format) protected function validateRegex($regex) { - return false !== @preg_match('#' . str_replace('#', '\\#', $regex) . '#u', ''); + return false !== @preg_match(self::jsonPatternToPhpRegex($regex), ''); } protected function validateColor($color) diff --git a/src/JsonSchema/Constraints/ObjectConstraint.php b/src/JsonSchema/Constraints/ObjectConstraint.php index 768b6793..b4f85650 100644 --- a/src/JsonSchema/Constraints/ObjectConstraint.php +++ b/src/JsonSchema/Constraints/ObjectConstraint.php @@ -56,7 +56,7 @@ public function validatePatternProperties($element, JsonPointer $path = null, $p { $matches = array(); foreach ($patternProperties as $pregex => $schema) { - $fullRegex = '#' . str_replace('#', '\\#', $pregex) . '#u'; + $fullRegex = self::jsonPatternToPhpRegex($pregex); // Validate the pattern before using it to test for matches if (@preg_match($fullRegex, '') === false) { diff --git a/src/JsonSchema/Constraints/StringConstraint.php b/src/JsonSchema/Constraints/StringConstraint.php index b3bdfbf7..1ccb23b0 100644 --- a/src/JsonSchema/Constraints/StringConstraint.php +++ b/src/JsonSchema/Constraints/StringConstraint.php @@ -40,7 +40,7 @@ public function check(&$element, $schema = null, JsonPointer $path = null, $i = } // Verify a regex pattern - if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) { + if (isset($schema->pattern) && !preg_match(self::jsonPatternToPhpRegex($schema->pattern), $element)) { $this->addError(ConstraintError::PATTERN(), $path, array( 'pattern' => $schema->pattern, )); From 330db08e286f21744fe1d7c55dfc0a8f42362373 Mon Sep 17 00:00:00 2001 From: Guilliam Xavier Date: Mon, 8 Feb 2021 11:05:49 +0100 Subject: [PATCH 3/3] Use a safer delimiter in jsonPatternToPhpRegex() `#` is escaped by preg_quote() in PHP >= 7.3, and `/` needs to be escaped currently (before #650), so let's go with `~` --- src/JsonSchema/Constraints/BaseConstraint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JsonSchema/Constraints/BaseConstraint.php b/src/JsonSchema/Constraints/BaseConstraint.php index 43498ee3..ee55922f 100644 --- a/src/JsonSchema/Constraints/BaseConstraint.php +++ b/src/JsonSchema/Constraints/BaseConstraint.php @@ -163,6 +163,6 @@ public static function arrayToObjectRecursive($array) */ public static function jsonPatternToPhpRegex($pattern) { - return '#' . str_replace('#', '\\#', $pattern) . '#u'; + return '~' . str_replace('~', '\\~', $pattern) . '~u'; } }