diff --git a/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php index 5d6c55cb..689ab3d2 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php @@ -73,13 +73,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ -// '\D' => '[^0-9]', -// '\d' => '[0-9]', - '\p{digit}' => '\p{Nd}', -// '\w' => '[A-Za-z0-9_]', -// '\W' => '[^A-Za-z0-9_]', -// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space, - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php index 5c705f4a..f97e4bd8 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php @@ -43,13 +43,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ - '\D' => '[^0-9]', - '\d' => '[0-9]', - '\p{digit}' => '[0-9]', - '\w' => '[A-Za-z0-9_]', - '\W' => '[^A-Za-z0-9_]', - '\s' => '[\s\x{200B}]', // Explicitly include zero width white space - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php index 969736bf..2b660533 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php @@ -52,13 +52,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ -// '\D' => '[^0-9]', - '\d' => '[0-9]', - '\p{digit}' => '[0-9]', -// '\w' => '[A-Za-z0-9_]', -// '\W' => '[^A-Za-z0-9_]', -// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php index 7162c11b..f354d86b 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php @@ -73,13 +73,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ -// '\D' => '[^0-9]', -// '\d' => '[0-9]', - '\p{digit}' => '\p{Nd}', -// '\w' => '[A-Za-z0-9_]', -// '\W' => '[^A-Za-z0-9_]', -// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space, - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php index 062b61d3..7d130d73 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php @@ -43,13 +43,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ - '\D' => '[^0-9]', - '\d' => '[0-9]', - '\p{digit}' => '[0-9]', - '\w' => '[A-Za-z0-9_]', - '\W' => '[^A-Za-z0-9_]', - '\s' => '[\s\x{200B}]', // Explicitly include zero width white space - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php index d821abf1..371c1db6 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php @@ -52,13 +52,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ -// '\D' => '[^0-9]', - '\d' => '[0-9]', - '\p{digit}' => '[0-9]', -// '\w' => '[A-Za-z0-9_]', -// '\W' => '[^A-Za-z0-9_]', -// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php index 9406c477..090d2fcb 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft2019/AdditionalPropertiesConstraint.php @@ -73,13 +73,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ -// '\D' => '[^0-9]', -// '\d' => '[0-9]', - '\p{digit}' => '\p{Nd}', -// '\w' => '[A-Za-z0-9_]', -// '\W' => '[^A-Za-z0-9_]', -// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space, - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php index b36ec026..a7f3a2dc 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft2019/PatternConstraint.php @@ -43,13 +43,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ - '\D' => '[^0-9]', - '\d' => '[0-9]', - '\p{digit}' => '[0-9]', - '\w' => '[A-Za-z0-9_]', - '\W' => '[^A-Za-z0-9_]', - '\s' => '[\s\x{200B}]', // Explicitly include zero width white space - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php b/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php index 44156154..88a746a2 100644 --- a/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php +++ b/src/JsonSchema/Constraints/Drafts/Draft2019/PatternPropertiesConstraint.php @@ -55,13 +55,16 @@ public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = n private function createPregMatchPattern(string $pattern): string { $replacements = [ -// '\D' => '[^0-9]', - '\d' => '[0-9]', - '\p{digit}' => '[0-9]', -// '\w' => '[A-Za-z0-9_]', -// '\W' => '[^A-Za-z0-9_]', -// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space - '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations + // PCRE with /u makes \d, \D, \w and \W Unicode aware, while ECMA-262 defines + // them over ASCII only, so they are narrowed back to their ECMA meaning. + '\\D' => '[^0-9]', + '\\d' => '[0-9]', + '\\w' => '[A-Za-z0-9_]', + '\\W' => '[^A-Za-z0-9_]', + '\\s' => '[\\s\\x{200B}]', // Explicitly include zero width white space + // PCRE rejects the ECMA long property names, so they are mapped to its abbreviations. + '\\p{digit}' => '\\p{Nd}', + '\\p{Letter}' => '\\p{L}', ]; $pattern = str_replace( diff --git a/tests/Constraints/Draft06/EcmaPatternTest.php b/tests/Constraints/Draft06/EcmaPatternTest.php new file mode 100644 index 00000000..5936fb52 --- /dev/null +++ b/tests/Constraints/Draft06/EcmaPatternTest.php @@ -0,0 +1,54 @@ +validate($decodedData, json_decode($schema), Constraint::CHECK_MODE_STRICT); + + self::assertSame($expectedToBeValid, $validator->isValid(), (string) json_encode($validator->getErrors())); + } + + /** + * @return array + */ + public static function provideNonAsciiCases(): array + { + $dialect = '"$schema": "http://json-schema.org/draft-06/schema#"'; + + return [ + 'ASCII digit matches \\d in patternProperties' => [ + '{' . $dialect . ', "patternProperties": {"^\\\\d+$": {"type": "string"}}, "additionalProperties": false}', + '{"5": "ok"}', + true, + ], + 'Arabic-Indic digit does not match \\d in patternProperties' => [ + '{' . $dialect . ', "patternProperties": {"^\\\\d+$": {"type": "string"}}, "additionalProperties": false}', + '{"٣": "ok"}', + false, + ], + 'accented letter does not match \\w in pattern' => [ + '{' . $dialect . ', "type": "string", "pattern": "^\\\\w+$"}', + '"é"', + false, + ], + ]; + } +} diff --git a/tests/Constraints/Draft07/EcmaPatternTest.php b/tests/Constraints/Draft07/EcmaPatternTest.php new file mode 100644 index 00000000..7147c6e1 --- /dev/null +++ b/tests/Constraints/Draft07/EcmaPatternTest.php @@ -0,0 +1,54 @@ +validate($decodedData, json_decode($schema), Constraint::CHECK_MODE_STRICT); + + self::assertSame($expectedToBeValid, $validator->isValid(), (string) json_encode($validator->getErrors())); + } + + /** + * @return array + */ + public static function provideNonAsciiCases(): array + { + $dialect = '"$schema": "http://json-schema.org/draft-07/schema#"'; + + return [ + 'ASCII digit matches \\d in patternProperties' => [ + '{' . $dialect . ', "patternProperties": {"^\\\\d+$": {"type": "string"}}, "additionalProperties": false}', + '{"5": "ok"}', + true, + ], + 'Arabic-Indic digit does not match \\d in patternProperties' => [ + '{' . $dialect . ', "patternProperties": {"^\\\\d+$": {"type": "string"}}, "additionalProperties": false}', + '{"٣": "ok"}', + false, + ], + 'accented letter does not match \\w in pattern' => [ + '{' . $dialect . ', "type": "string", "pattern": "^\\\\w+$"}', + '"é"', + false, + ], + ]; + } +} diff --git a/tests/Constraints/Draft2019/EcmaPatternTest.php b/tests/Constraints/Draft2019/EcmaPatternTest.php new file mode 100644 index 00000000..eda96a7a --- /dev/null +++ b/tests/Constraints/Draft2019/EcmaPatternTest.php @@ -0,0 +1,54 @@ +validate($decodedData, json_decode($schema), Constraint::CHECK_MODE_STRICT); + + self::assertSame($expectedToBeValid, $validator->isValid(), (string) json_encode($validator->getErrors())); + } + + /** + * @return array + */ + public static function provideNonAsciiCases(): array + { + $dialect = '"$schema": "https://json-schema.org/draft/2019-09/schema"'; + + return [ + 'ASCII digit matches \\d in patternProperties' => [ + '{' . $dialect . ', "patternProperties": {"^\\\\d+$": {"type": "string"}}, "additionalProperties": false}', + '{"5": "ok"}', + true, + ], + 'Arabic-Indic digit does not match \\d in patternProperties' => [ + '{' . $dialect . ', "patternProperties": {"^\\\\d+$": {"type": "string"}}, "additionalProperties": false}', + '{"٣": "ok"}', + false, + ], + 'accented letter does not match \\w in pattern' => [ + '{' . $dialect . ', "type": "string", "pattern": "^\\\\w+$"}', + '"é"', + false, + ], + ]; + } +}