Skip to content

Validators: allow pipe character inside a pattern: regular expression - #340

Open
dualfroz wants to merge 1 commit into
nette:masterfrom
dualfroz:dualfroz/fix-pattern-validator-delimiter
Open

Validators: allow pipe character inside a pattern: regular expression#340
dualfroz wants to merge 1 commit into
nette:masterfrom
dualfroz:dualfroz/fix-pattern-validator-delimiter

Conversation

@dualfroz

@dualfroz dualfroz commented Sep 5, 2026

Copy link
Copy Markdown

Problem

Validators::is() crashes on a valid pattern that contains a pipe:

Validators::is('a', 'pattern:(a|b)');
// Nette\Utils\RegexpException: Compilation failed: missing closing parenthesis
// at offset 4 in pattern: |^(a$|D

The call should simply return true. This has been reported and confirmed valid
in #206.

Root cause (two intertwined parsing bugs)

src/Utils/Validators.php, method is():

  1. Spec splitting (was line ~140): explode('|', $expected) splits the whole
    expression on every |. It is meant to separate combined validators
    (int|string), but it also tears a regular expression apart, so
    pattern:(a|b) becomes the two tokens pattern:(a and b).

  2. PCRE delimiter collision (was line ~164):
    Strings::match($value, '|^' . $pattern . '$|D') uses | as the PCRE
    delimiter. Even without bug 1, any | in the user pattern would prematurely
    close the delimiter and produce an invalid regex.

Together they turn a valid pattern into |^(a$|D, which fails to compile and
throws RegexpException.

Fix

  • A pattern: argument now consumes the remaining part of the expression, so the
    pipes it contains are no longer treated as the validator separator. Combined
    forms such as int|pattern:(a|b) keep working because the pipe-as-OR semantics
    are preserved for every non-pattern validator.
  • The PCRE delimiter is changed from | to ~, which does not collide with the
    common alternation metacharacter. The pattern is additionally wrapped in a
    non-capturing group ^(?:...)$ so a bare alternation (pattern:a|b) is
    anchored as a whole instead of being read as (^a)|(b$).

The int|string, pattern:\d+ and empty pattern cases are unchanged.

The 'pattern:' argument of Validators::is() crashed on a valid regular
expression containing a pipe, e.g. Validators::is('a', 'pattern:(a|b)').

Two parsing bugs combined: the expression was split into validators on every
'|', tearing the regexp apart, and the pipe was also used as the PCRE
delimiter. A 'pattern:' argument now consumes the rest of the expression and
the delimiter is changed to '~', with the pattern anchored as a whole so bare
alternations work too. Pipe-as-OR semantics for combined validators are kept.

Fixes nette#206
@dualfroz
dualfroz force-pushed the dualfroz/fix-pattern-validator-delimiter branch from bb71d24 to dc5e00b Compare September 5, 2026 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant