Skip to content

Commit eaa7fd1

Browse files
committed
Improve robustness of geo shape parser for malformed shapes
Ensures that malformed geoshapes are more reliably ignored if "ignore_malformed" is set to true instead of failing the entire document. Closes elastic#31428
1 parent ee59761 commit eaa7fd1

File tree

2 files changed

+114
-48
lines changed

2 files changed

+114
-48
lines changed

server/src/main/java/org/elasticsearch/common/geo/parsers/GeoJsonParser.java

+68-48
Original file line numberDiff line numberDiff line change
@@ -55,57 +55,66 @@ protected static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper s
5555
String malformedException = null;
5656

5757
XContentParser.Token token;
58-
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
59-
if (token == XContentParser.Token.FIELD_NAME) {
60-
String fieldName = parser.currentName();
61-
62-
if (ShapeParser.FIELD_TYPE.match(fieldName, parser.getDeprecationHandler())) {
63-
parser.nextToken();
64-
final GeoShapeType type = GeoShapeType.forName(parser.text());
65-
if (shapeType != null && shapeType.equals(type) == false) {
66-
malformedException = ShapeParser.FIELD_TYPE + " already parsed as ["
67-
+ shapeType + "] cannot redefine as [" + type + "]";
58+
try {
59+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
60+
if (token == XContentParser.Token.FIELD_NAME) {
61+
String fieldName = parser.currentName();
62+
63+
if (ShapeParser.FIELD_TYPE.match(fieldName, parser.getDeprecationHandler())) {
64+
parser.nextToken();
65+
final GeoShapeType type = GeoShapeType.forName(parser.text());
66+
if (shapeType != null && shapeType.equals(type) == false) {
67+
malformedException = ShapeParser.FIELD_TYPE + " already parsed as ["
68+
+ shapeType + "] cannot redefine as [" + type + "]";
69+
} else {
70+
shapeType = type;
71+
}
72+
} else if (ShapeParser.FIELD_COORDINATES.match(fieldName, parser.getDeprecationHandler())) {
73+
parser.nextToken();
74+
CoordinateNode tempNode = parseCoordinates(parser, ignoreZValue.value());
75+
if (coordinateNode != null && tempNode.numDimensions() != coordinateNode.numDimensions()) {
76+
throw new ElasticsearchParseException("Exception parsing coordinates: " +
77+
"number of dimensions do not match");
78+
}
79+
coordinateNode = tempNode;
80+
} else if (ShapeParser.FIELD_GEOMETRIES.match(fieldName, parser.getDeprecationHandler())) {
81+
if (shapeType == null) {
82+
shapeType = GeoShapeType.GEOMETRYCOLLECTION;
83+
} else if (shapeType.equals(GeoShapeType.GEOMETRYCOLLECTION) == false) {
84+
malformedException = "cannot have [" + ShapeParser.FIELD_GEOMETRIES + "] with type set to ["
85+
+ shapeType + "]";
86+
}
87+
parser.nextToken();
88+
geometryCollections = parseGeometries(parser, shapeMapper);
89+
} else if (CircleBuilder.FIELD_RADIUS.match(fieldName, parser.getDeprecationHandler())) {
90+
if (shapeType == null) {
91+
shapeType = GeoShapeType.CIRCLE;
92+
} else if (shapeType != null && shapeType.equals(GeoShapeType.CIRCLE) == false) {
93+
malformedException = "cannot have [" + CircleBuilder.FIELD_RADIUS + "] with type set to ["
94+
+ shapeType + "]";
95+
}
96+
parser.nextToken();
97+
radius = DistanceUnit.Distance.parseDistance(parser.text());
98+
} else if (ShapeParser.FIELD_ORIENTATION.match(fieldName, parser.getDeprecationHandler())) {
99+
if (shapeType != null
100+
&& (shapeType.equals(GeoShapeType.POLYGON) || shapeType.equals(GeoShapeType.MULTIPOLYGON)) == false) {
101+
malformedException = "cannot have [" + ShapeParser.FIELD_ORIENTATION + "] with type set to [" + shapeType + "]";
102+
}
103+
parser.nextToken();
104+
requestedOrientation = ShapeBuilder.Orientation.fromString(parser.text());
68105
} else {
69-
shapeType = type;
106+
parser.nextToken();
107+
parser.skipChildren();
70108
}
71-
} else if (ShapeParser.FIELD_COORDINATES.match(fieldName, parser.getDeprecationHandler())) {
72-
parser.nextToken();
73-
CoordinateNode tempNode = parseCoordinates(parser, ignoreZValue.value());
74-
if (coordinateNode != null && tempNode.numDimensions() != coordinateNode.numDimensions()) {
75-
throw new ElasticsearchParseException("Exception parsing coordinates: " +
76-
"number of dimensions do not match");
77-
}
78-
coordinateNode = tempNode;
79-
} else if (ShapeParser.FIELD_GEOMETRIES.match(fieldName, parser.getDeprecationHandler())) {
80-
if (shapeType == null) {
81-
shapeType = GeoShapeType.GEOMETRYCOLLECTION;
82-
} else if (shapeType.equals(GeoShapeType.GEOMETRYCOLLECTION) == false) {
83-
malformedException = "cannot have [" + ShapeParser.FIELD_GEOMETRIES + "] with type set to ["
84-
+ shapeType + "]";
85-
}
86-
parser.nextToken();
87-
geometryCollections = parseGeometries(parser, shapeMapper);
88-
} else if (CircleBuilder.FIELD_RADIUS.match(fieldName, parser.getDeprecationHandler())) {
89-
if (shapeType == null) {
90-
shapeType = GeoShapeType.CIRCLE;
91-
} else if (shapeType != null && shapeType.equals(GeoShapeType.CIRCLE) == false) {
92-
malformedException = "cannot have [" + CircleBuilder.FIELD_RADIUS + "] with type set to ["
93-
+ shapeType + "]";
94-
}
95-
parser.nextToken();
96-
radius = DistanceUnit.Distance.parseDistance(parser.text());
97-
} else if (ShapeParser.FIELD_ORIENTATION.match(fieldName, parser.getDeprecationHandler())) {
98-
if (shapeType != null
99-
&& (shapeType.equals(GeoShapeType.POLYGON) || shapeType.equals(GeoShapeType.MULTIPOLYGON)) == false) {
100-
malformedException = "cannot have [" + ShapeParser.FIELD_ORIENTATION + "] with type set to [" + shapeType + "]";
101-
}
102-
parser.nextToken();
103-
requestedOrientation = ShapeBuilder.Orientation.fromString(parser.text());
104-
} else {
105-
parser.nextToken();
106-
parser.skipChildren();
107109
}
108110
}
111+
} catch (Exception ex) {
112+
// Skip all other fields until the end of the object
113+
while (parser.currentToken() != XContentParser.Token.END_OBJECT && parser.currentToken() != null) {
114+
parser.nextToken();
115+
parser.skipChildren();
116+
}
117+
throw ex;
109118
}
110119

111120
if (malformedException != null) {
@@ -144,6 +153,12 @@ protected static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper s
144153
* XContentParser
145154
*/
146155
private static CoordinateNode parseCoordinates(XContentParser parser, boolean ignoreZValue) throws IOException {
156+
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
157+
parser.skipChildren();
158+
parser.nextToken();
159+
throw new ElasticsearchParseException("coordinates cannot be specified as objects");
160+
}
161+
147162
XContentParser.Token token = parser.nextToken();
148163
// Base cases
149164
if (token != XContentParser.Token.START_ARRAY &&
@@ -168,8 +183,13 @@ private static CoordinateNode parseCoordinates(XContentParser parser, boolean ig
168183
}
169184

170185
private static Coordinate parseCoordinate(XContentParser parser, boolean ignoreZValue) throws IOException {
186+
if (parser.currentToken() != XContentParser.Token.VALUE_NUMBER) {
187+
throw new ElasticsearchParseException("geo coordinates must be numbers");
188+
}
171189
double lon = parser.doubleValue();
172-
parser.nextToken();
190+
if (parser.nextToken() != XContentParser.Token.VALUE_NUMBER) {
191+
throw new ElasticsearchParseException("geo coordinates must be numbers");
192+
}
173193
double lat = parser.doubleValue();
174194
XContentParser.Token token = parser.nextToken();
175195
// alt (for storing purposes only - future use includes 3d shapes)

server/src/test/java/org/elasticsearch/common/geo/GeoJsonShapeParserTests.java

+46
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ public void testParseMultiDimensionShapes() throws IOException {
145145
XContentParser parser = createParser(pointGeoJson);
146146
parser.nextToken();
147147
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
148+
assertNull(parser.nextToken());
148149

149150
// multi dimension linestring
150151
XContentBuilder lineGeoJson = XContentFactory.jsonBuilder()
@@ -159,6 +160,7 @@ public void testParseMultiDimensionShapes() throws IOException {
159160
parser = createParser(lineGeoJson);
160161
parser.nextToken();
161162
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
163+
assertNull(parser.nextToken());
162164
}
163165

164166
@Override
@@ -196,6 +198,7 @@ public void testParseEnvelope() throws IOException {
196198
XContentParser parser = createParser(multilinesGeoJson);
197199
parser.nextToken();
198200
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
201+
assertNull(parser.nextToken());
199202

200203
// test #4: "envelope" with empty coordinates
201204
multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope")
@@ -205,6 +208,7 @@ public void testParseEnvelope() throws IOException {
205208
parser = createParser(multilinesGeoJson);
206209
parser.nextToken();
207210
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
211+
assertNull(parser.nextToken());
208212
}
209213

210214
@Override
@@ -288,6 +292,7 @@ public void testInvalidDimensionalPolygon() throws IOException {
288292
XContentParser parser = createParser(polygonGeoJson);
289293
parser.nextToken();
290294
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
295+
assertNull(parser.nextToken());
291296
}
292297

293298
public void testParseInvalidPoint() throws IOException {
@@ -302,6 +307,7 @@ public void testParseInvalidPoint() throws IOException {
302307
XContentParser parser = createParser(invalidPoint1);
303308
parser.nextToken();
304309
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
310+
assertNull(parser.nextToken());
305311

306312
// test case 2: create an invalid point object with an empty number of coordinates
307313
XContentBuilder invalidPoint2 = XContentFactory.jsonBuilder()
@@ -313,6 +319,7 @@ public void testParseInvalidPoint() throws IOException {
313319
parser = createParser(invalidPoint2);
314320
parser.nextToken();
315321
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
322+
assertNull(parser.nextToken());
316323
}
317324

318325
public void testParseInvalidMultipoint() throws IOException {
@@ -325,6 +332,7 @@ public void testParseInvalidMultipoint() throws IOException {
325332
XContentParser parser = createParser(invalidMultipoint1);
326333
parser.nextToken();
327334
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
335+
assertNull(parser.nextToken());
328336

329337
// test case 2: create an invalid multipoint object with null coordinate
330338
XContentBuilder invalidMultipoint2 = XContentFactory.jsonBuilder()
@@ -336,6 +344,7 @@ public void testParseInvalidMultipoint() throws IOException {
336344
parser = createParser(invalidMultipoint2);
337345
parser.nextToken();
338346
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
347+
assertNull(parser.nextToken());
339348

340349
// test case 3: create a valid formatted multipoint object with invalid number (0) of coordinates
341350
XContentBuilder invalidMultipoint3 = XContentFactory.jsonBuilder()
@@ -348,6 +357,7 @@ public void testParseInvalidMultipoint() throws IOException {
348357
parser = createParser(invalidMultipoint3);
349358
parser.nextToken();
350359
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
360+
assertNull(parser.nextToken());
351361
}
352362

353363
public void testParseInvalidMultiPolygon() throws IOException {
@@ -383,6 +393,7 @@ public void testParseInvalidMultiPolygon() throws IOException {
383393
XContentParser parser = createParser(JsonXContent.jsonXContent, multiPolygonGeoJson);
384394
parser.nextToken();
385395
ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class);
396+
assertNull(parser.nextToken());
386397
}
387398

388399
public void testParseInvalidDimensionalMultiPolygon() throws IOException {
@@ -422,6 +433,7 @@ public void testParseInvalidDimensionalMultiPolygon() throws IOException {
422433
XContentParser parser = createParser(JsonXContent.jsonXContent, multiPolygonGeoJson);
423434
parser.nextToken();
424435
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
436+
assertNull(parser.nextToken());
425437
}
426438

427439

@@ -630,6 +642,7 @@ public void testParseInvalidPolygon() throws IOException {
630642
XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly);
631643
parser.nextToken();
632644
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
645+
assertNull(parser.nextToken());
633646

634647
// test case 2: create an invalid polygon with only 1 point
635648
invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon")
@@ -643,6 +656,7 @@ public void testParseInvalidPolygon() throws IOException {
643656
parser = createParser(JsonXContent.jsonXContent, invalidPoly);
644657
parser.nextToken();
645658
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
659+
assertNull(parser.nextToken());
646660

647661
// test case 3: create an invalid polygon with 0 points
648662
invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon")
@@ -656,6 +670,7 @@ public void testParseInvalidPolygon() throws IOException {
656670
parser = createParser(JsonXContent.jsonXContent, invalidPoly);
657671
parser.nextToken();
658672
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
673+
assertNull(parser.nextToken());
659674

660675
// test case 4: create an invalid polygon with null value points
661676
invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon")
@@ -669,6 +684,7 @@ public void testParseInvalidPolygon() throws IOException {
669684
parser = createParser(JsonXContent.jsonXContent, invalidPoly);
670685
parser.nextToken();
671686
ElasticsearchGeoAssertions.assertValidException(parser, IllegalArgumentException.class);
687+
assertNull(parser.nextToken());
672688

673689
// test case 5: create an invalid polygon with 1 invalid LinearRing
674690
invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon")
@@ -680,6 +696,7 @@ public void testParseInvalidPolygon() throws IOException {
680696
parser = createParser(JsonXContent.jsonXContent, invalidPoly);
681697
parser.nextToken();
682698
ElasticsearchGeoAssertions.assertValidException(parser, IllegalArgumentException.class);
699+
assertNull(parser.nextToken());
683700

684701
// test case 6: create an invalid polygon with 0 LinearRings
685702
invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon")
@@ -689,6 +706,7 @@ public void testParseInvalidPolygon() throws IOException {
689706
parser = createParser(JsonXContent.jsonXContent, invalidPoly);
690707
parser.nextToken();
691708
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
709+
assertNull(parser.nextToken());
692710

693711
// test case 7: create an invalid polygon with 0 LinearRings
694712
invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon")
@@ -700,6 +718,7 @@ public void testParseInvalidPolygon() throws IOException {
700718
parser = createParser(JsonXContent.jsonXContent, invalidPoly);
701719
parser.nextToken();
702720
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
721+
assertNull(parser.nextToken());
703722
}
704723

705724
public void testParsePolygonWithHole() throws IOException {
@@ -767,6 +786,7 @@ public void testParseSelfCrossingPolygon() throws IOException {
767786
XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson);
768787
parser.nextToken();
769788
ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class);
789+
assertNull(parser.nextToken());
770790
}
771791

772792
@Override
@@ -1131,4 +1151,30 @@ public void testParseOrientationOption() throws IOException {
11311151

11321152
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
11331153
}
1154+
1155+
public void testParseInvalidShapes() throws IOException {
1156+
// single dimensions point
1157+
XContentBuilder tooLittlePointGeoJson = XContentFactory.jsonBuilder()
1158+
.startObject()
1159+
.field("type", "Point")
1160+
.startArray("coordinates").value(10.0).endArray()
1161+
.endObject();
1162+
1163+
XContentParser parser = createParser(tooLittlePointGeoJson);
1164+
parser.nextToken();
1165+
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
1166+
assertNull(parser.nextToken());
1167+
1168+
// zero dimensions point
1169+
XContentBuilder emptyPointGeoJson = XContentFactory.jsonBuilder()
1170+
.startObject()
1171+
.field("type", "Point")
1172+
.startObject("coordinates").field("foo", "bar").endObject()
1173+
.endObject();
1174+
1175+
parser = createParser(emptyPointGeoJson);
1176+
parser.nextToken();
1177+
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
1178+
assertNull(parser.nextToken());
1179+
}
11341180
}

0 commit comments

Comments
 (0)