Do not fail with ISE if default change query contains quoted invalid regex

If a default query contains an invalid regex, e.g. "^[A", parsing the
query fails and the request is rejected as '400 Bad Request' (the
parsing fails because '[' is a syntax character in our Antlr syntax
specification). If the invalid regex is quoted the parsing succeeded,
but then creating the Automaton failed with an IllegalArgumentException
which caused a '500 Internal Sever Error'. We now catch the
IllegalArgumentException and wrap it into a QueryParseException so that
the user gets a '400 Bad Request'.  This is better than failing, but not
ideal yet. Ideally we would ignore the QueryParseException for the
default query and just have no match, but if we do that executing the
query fails in Lucene which would again be an internal server error.

Change-Id: Ic1d18919ca8a6a8d21156b7b3bf3b96aeb382624
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2020-02-26 12:34:38 +01:00
parent b4cddf0227
commit f9ee2e4641
3 changed files with 31 additions and 4 deletions

View File

@@ -703,7 +703,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
} }
@Operator @Operator
public Predicate<ChangeData> branch(String name) { public Predicate<ChangeData> branch(String name) throws QueryParseException {
if (name.startsWith("^")) { if (name.startsWith("^")) {
return ref("^" + RefNames.fullName(name.substring(1))); return ref("^" + RefNames.fullName(name.substring(1)));
} }
@@ -732,7 +732,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData, ChangeQueryBuil
} }
@Operator @Operator
public Predicate<ChangeData> ref(String ref) { public Predicate<ChangeData> ref(String ref) throws QueryParseException {
if (ref.startsWith("^")) { if (ref.startsWith("^")) {
return new RegexRefPredicate(ref); return new RegexRefPredicate(ref);
} }

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.query.change; package com.google.gerrit.server.query.change;
import com.google.gerrit.entities.Change; import com.google.gerrit.entities.Change;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.server.index.change.ChangeField; import com.google.gerrit.server.index.change.ChangeField;
import dk.brics.automaton.RegExp; import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton; import dk.brics.automaton.RunAutomaton;
@@ -22,7 +23,7 @@ import dk.brics.automaton.RunAutomaton;
public class RegexRefPredicate extends ChangeRegexPredicate { public class RegexRefPredicate extends ChangeRegexPredicate {
protected final RunAutomaton pattern; protected final RunAutomaton pattern;
public RegexRefPredicate(String re) { public RegexRefPredicate(String re) throws QueryParseException {
super(ChangeField.REF, re); super(ChangeField.REF, re);
if (re.startsWith("^")) { if (re.startsWith("^")) {
@@ -33,7 +34,11 @@ public class RegexRefPredicate extends ChangeRegexPredicate {
re = re.substring(0, re.length() - 1); re = re.substring(0, re.length() - 1);
} }
this.pattern = new RunAutomaton(new RegExp(re).toAutomaton()); try {
this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
} catch (IllegalArgumentException e) {
throw new QueryParseException(String.format("invalid regular expression: %s", re), e);
}
} }
@Override @Override

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.acceptance.api.change;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block; import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static javax.servlet.http.HttpServletResponse.SC_OK; import static javax.servlet.http.HttpServletResponse.SC_OK;
@@ -31,6 +32,7 @@ import com.google.gerrit.common.data.Permission;
import com.google.gerrit.entities.Account; import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Project; import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.server.restapi.change.QueryChanges; import com.google.gerrit.server.restapi.change.QueryChanges;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -225,6 +227,26 @@ public class QueryChangeIT extends AbstractDaemonTest {
assertThat(queryChanges.apply(TopLevelResource.INSTANCE).statusCode()).isEqualTo(SC_OK); assertThat(queryChanges.apply(TopLevelResource.INSTANCE).statusCode()).isEqualTo(SC_OK);
} }
@Test
public void defaultQueryCannotBeParsedDueToInvalidRegEx() throws Exception {
QueryChanges queryChanges = queryChangesProvider.get();
queryChanges.addQuery("^[A");
BadRequestException e =
assertThrows(
BadRequestException.class, () -> queryChanges.apply(TopLevelResource.INSTANCE));
assertThat(e).hasMessageThat().contains("no viable alternative at character '['");
}
@Test
public void defaultQueryWithInvalidQuotedRegEx() throws Exception {
QueryChanges queryChanges = queryChangesProvider.get();
queryChanges.addQuery("\"^[A\"");
BadRequestException e =
assertThrows(
BadRequestException.class, () -> queryChanges.apply(TopLevelResource.INSTANCE));
assertThat(e).hasMessageThat().isEqualTo("invalid regular expression: [A");
}
private static void assertNoChangeHasMoreChangesSet(List<ChangeInfo> results) { private static void assertNoChangeHasMoreChangesSet(List<ChangeInfo> results) {
for (ChangeInfo info : results) { for (ChangeInfo info : results) {
assertThat(info._moreChanges).isNull(); assertThat(info._moreChanges).isNull();