| @@ -28,11 +28,13 @@ | ||
| 28 | 28 | */ |
| 29 | 29 | class Parser implements ParserInterface |
| 30 | 30 | { |
| 31 | 31 | private const HAS_NESTING_LIMIT = 16; |
| 32 | + private const NESTING_LIMIT = 16; | |
| 32 | 33 | |
| 33 | 34 | private Tokenizer $tokenizer; |
| 34 | 35 | private int $hasNestingDepth = 0; |
| 36 | + private int $nestingDepth = 0; | |
| 35 | 37 | |
| 36 | 38 | public function __construct(?Tokenizer $tokenizer = null) |
| 37 | 39 | { |
| 38 | 40 | $this->tokenizer = $tokenizer ?? new Tokenizer(); |
| @@ -303,9 +305,9 @@ | ||
| 303 | 305 | } |
| 304 | 306 | |
| 305 | 307 | $result = new Node\NegationNode($result, $argument); |
| 306 | 308 | } elseif ('is' === strtolower($identifier)) { |
| 307 | - $selectors = $this->parseSelectorList($stream, true); | |
| 309 | + $selectors = $this->parseNestedSelectorList($stream, 'is'); | |
| 308 | 310 | |
| 309 | 311 | $next = $stream->getNext(); |
| 310 | 312 | if (!$next->isDelimiter([')'])) { |
| 311 | 313 | throw SyntaxErrorException::unexpectedToken('")"', $next); |
| @@ -312,9 +314,9 @@ | ||
| 312 | 314 | } |
| 313 | 315 | |
| 314 | 316 | $result = new Node\MatchingNode($result, $selectors); |
| 315 | 317 | } elseif ('where' === strtolower($identifier)) { |
| 316 | - $selectors = $this->parseSelectorList($stream, true); | |
| 318 | + $selectors = $this->parseNestedSelectorList($stream, 'where'); | |
| 317 | 319 | |
| 318 | 320 | $next = $stream->getNext(); |
| 319 | 321 | if (!$next->isDelimiter([')'])) { |
| 320 | 322 | throw SyntaxErrorException::unexpectedToken('")"', $next); |
| @@ -359,8 +361,28 @@ | ||
| 359 | 361 | throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek()); |
| 360 | 362 | } |
| 361 | 363 | |
| 362 | 364 | return [$result, $pseudoElement]; |
| 365 | + } | |
| 366 | + | |
| 367 | + /** | |
| 368 | + * @return Node\SelectorNode[] | |
| 369 | + * | |
| 370 | + * @throws SyntaxErrorException | |
| 371 | + */ | |
| 372 | + private function parseNestedSelectorList(TokenStream $stream, string $identifier): array | |
| 373 | + { | |
| 374 | + if ($this->nestingDepth >= self::NESTING_LIMIT) { | |
| 375 | + throw new SyntaxErrorException(\sprintf('Got too deeply nested :%s().', $identifier)); | |
| 376 | + } | |
| 377 | + | |
| 378 | + ++$this->nestingDepth; | |
| 379 | + | |
| 380 | + try { | |
| 381 | + return $this->parseSelectorList($stream, true); | |
| 382 | + } finally { | |
| 383 | + --$this->nestingDepth; | |
| 384 | + } | |
| 363 | 385 | } |
| 364 | 386 | |
| 365 | 387 | private function parseElementNode(TokenStream $stream): Node\ElementNode |
| 366 | 388 | { |