Skip to content

Commit c4dd25e

Browse files
authored
Disallow consecutive combinators in CSS selectors (#2311)
1 parent f70a7bc commit c4dd25e

File tree

2 files changed

+25
-5
lines changed

2 files changed

+25
-5
lines changed

src/main/java/org/jsoup/select/QueryParser.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static Evaluator parse(String query) {
5555
Parse the query. We use this simplified expression of the grammar:
5656
<pre>
5757
SelectorGroup ::= Selector (',' Selector)*
58-
Selector ::= SimpleSequence ( Combinator SimpleSequence )*
58+
Selector ::= [ Combinator ] SimpleSequence ( Combinator SimpleSequence )*
5959
SimpleSequence ::= [ TypeSelector ] ( ID | Class | Attribute | Pseudo )*
6060
Pseudo ::= ':' Name [ '(' SelectorGroup ')' ]
6161
Combinator ::= S+ // descendant (whitespace)
@@ -85,8 +85,16 @@ Evaluator parseSelectorGroup() {
8585
}
8686

8787
Evaluator parseSelector() {
88-
// SimpleSequence ( Combinator SimpleSequence )*
89-
Evaluator left = parseSimpleSequence();
88+
// Selector ::= [ Combinator ] SimpleSequence ( Combinator SimpleSequence )*
89+
tq.consumeWhitespace();
90+
91+
Evaluator left;
92+
if (tq.matchesAny(Combinators)) {
93+
// e.g. query is "> div"; left side is root element
94+
left = new StructuralEvaluator.Root();
95+
} else {
96+
left = parseSimpleSequence();
97+
}
9098

9199
while (true) {
92100
char combinator = 0;
@@ -117,8 +125,6 @@ Evaluator parseSimpleSequence() {
117125
left = byTag();
118126
else if (tq.matchChomp('*'))
119127
left = new Evaluator.AllElements();
120-
else if (tq.matchesAny(Combinators)) // e.g. query is "> div"; type is root element
121-
left = new StructuralEvaluator.Root();
122128

123129
// zero or more subclasses (#, ., [)
124130
while(true) {

src/test/java/org/jsoup/select/QueryParserTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,18 @@ public void exceptOnUnhandledEvaluator() {
214214
assertThrows(SelectorParseException.class, () -> QueryParser.parse("div:has(p))"));
215215
assertEquals("Could not parse query 'div:has(p))': unexpected token at ')'", exception.getMessage());
216216
}
217+
218+
@Test void consecutiveCombinators() {
219+
Selector.SelectorParseException exception1 =
220+
assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse("div>>p"));
221+
assertEquals(
222+
"Could not parse query 'div>>p': unexpected token at '>p'",
223+
exception1.getMessage());
224+
225+
Selector.SelectorParseException exception2 =
226+
assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse("+ + div"));
227+
assertEquals(
228+
"Could not parse query '+ + div': unexpected token at '+ div'",
229+
exception2.getMessage());
230+
}
217231
}

0 commit comments

Comments
 (0)