diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java b/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java index 11053b6cdadd6..6dde6ec3b9202 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryParseContext.java @@ -66,6 +66,15 @@ public boolean isDeprecatedSetting(String setting) { public QueryBuilder parseTopLevelQueryBuilder() { try { QueryBuilder queryBuilder = null; + XContentParser.Token first = parser.nextToken(); + if (first == null) { + return null; + } else if (first != XContentParser.Token.START_OBJECT) { + throw new ParsingException( + parser.getTokenLocation(), "Expected [" + XContentParser.Token.START_OBJECT + + "] but found [" + first + "]", parser.getTokenLocation() + ); + } for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) { if (token == XContentParser.Token.FIELD_NAME) { String fieldName = parser.currentName(); @@ -110,7 +119,7 @@ public Optional parseInnerQueryBuilder() throws IOException { Optional result; try { @SuppressWarnings("unchecked") - Optional resultCast = (Optional) parser.namedObject(Optional.class, queryName, this); + Optional resultCast = (Optional) parser.namedObject(Optional.class, queryName, this); result = resultCast; } catch (UnknownNamedObjectException e) { // Preserve the error message from 5.0 until we have a compellingly better message so we don't break BWC. diff --git a/core/src/test/java/org/elasticsearch/rest/action/RestActionsTests.java b/core/src/test/java/org/elasticsearch/rest/action/RestActionsTests.java new file mode 100644 index 0000000000000..401cc79b02092 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/rest/action/RestActionsTests.java @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.rest.action; + +import com.fasterxml.jackson.core.io.JsonEOFException; +import java.util.Arrays; +import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.index.query.MatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.SearchModule; +import org.elasticsearch.test.ESTestCase; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.io.IOException; + +import static java.util.Collections.emptyList; + +public class RestActionsTests extends ESTestCase { + + private static NamedXContentRegistry xContentRegistry; + + @BeforeClass + public static void init() { + xContentRegistry = new NamedXContentRegistry(new SearchModule(Settings.EMPTY, false, emptyList()).getNamedXContents()); + } + + @AfterClass + public static void cleanup() { + xContentRegistry = null; + } + + public void testParseTopLevelBuilder() throws IOException { + QueryBuilder query = new MatchQueryBuilder("foo", "bar"); + String requestBody = "{ \"query\" : " + query.toString() + "}"; + try (XContentParser parser = createParser(JsonXContent.jsonXContent, requestBody)) { + QueryBuilder actual = RestActions.getQueryContent(parser); + assertEquals(query, actual); + } + } + + public void testParseTopLevelBuilderEmptyObject() throws IOException { + for (String requestBody : Arrays.asList("{}", "")) { + try (XContentParser parser = createParser(JsonXContent.jsonXContent, requestBody)) { + QueryBuilder query = RestActions.getQueryContent(parser); + assertNull(query); + } + } + } + + public void testParseTopLevelBuilderMalformedJson() throws IOException { + for (String requestBody : Arrays.asList("\"\"", "\"someString\"", "\"{\"")) { + try (XContentParser parser = createParser(JsonXContent.jsonXContent, requestBody)) { + ParsingException exception = + expectThrows(ParsingException.class, () -> RestActions.getQueryContent(parser)); + assertEquals("Expected [START_OBJECT] but found [VALUE_STRING]", exception.getMessage()); + } + } + } + + public void testParseTopLevelBuilderIncompleteJson() throws IOException { + for (String requestBody : Arrays.asList("{", "{ \"query\" :")) { + try (XContentParser parser = createParser(JsonXContent.jsonXContent, requestBody)) { + ParsingException exception = + expectThrows(ParsingException.class, () -> RestActions.getQueryContent(parser)); + assertEquals("Failed to parse", exception.getMessage()); + assertEquals(JsonEOFException.class, exception.getRootCause().getClass()); + } + } + } + + public void testParseTopLevelBuilderUnknownParameter() throws IOException { + String requestBody = "{ \"foo\" : \"bar\"}"; + try (XContentParser parser = createParser(JsonXContent.jsonXContent, requestBody)) { + ParsingException exception = expectThrows(ParsingException.class, () -> RestActions.getQueryContent(parser)); + assertEquals("request does not support [foo]", exception.getMessage()); + } + } + + @Override + protected NamedXContentRegistry xContentRegistry() { + return xContentRegistry; + } + +}