Skip to content

Commit

Permalink
Adding input validations for spatial coordinates. (#1704)
Browse files Browse the repository at this point in the history
  • Loading branch information
sreekanth-cb authored Jun 16, 2022
1 parent efd679a commit dee0c77
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 41 deletions.
77 changes: 67 additions & 10 deletions geo/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,10 @@ func extract2DCoordinates(thing interface{}) [][]float64 {
for j := 0; j < thingVal.Len(); j++ {
edges := thingVal.Index(j).Interface()
if es, ok := edges.([]interface{}); ok {
rv = append(rv, extractCoordinates(es))
v := extractCoordinates(es)
if len(v) == 2 {
rv = append(rv, v)
}
}
}

Expand Down Expand Up @@ -272,29 +275,38 @@ func extract4DCoordinates(thing interface{}) (rv [][][][]float64) {
return rv
}

func extractGeoShape(thing interface{}) ([][][][]float64, string, bool) {
func ParseGeoShapeField(thing interface{}) (interface{}, string, error) {
thingVal := reflect.ValueOf(thing)
if !thingVal.IsValid() {
return nil, "", false
return nil, "", nil
}

var shape string
var coordValue interface{}
var typ string

if thingVal.Kind() == reflect.Map {
iter := thingVal.MapRange()
for iter.Next() {
if iter.Key().String() == "type" {
typ = iter.Value().Interface().(string)
// make the shape type case insensitive.
typ = strings.ToLower(typ)
shape = iter.Value().Interface().(string)
continue
}

if iter.Key().String() == "coordinates" {
coordValue = iter.Value().Interface()
}
}
}

return coordValue, strings.ToLower(shape), nil
}

func extractGeoShape(thing interface{}) ([][][][]float64, string, bool) {
coordValue, typ, err := ParseGeoShapeField(thing)
if err != nil {
return nil, "", false
}

return ExtractGeoShapeCoordinates(coordValue, typ)
}

Expand Down Expand Up @@ -383,23 +395,68 @@ func ExtractGeoShapeCoordinates(coordValue interface{},
typ string) ([][][][]float64, string, bool) {
var rv [][][][]float64
if typ == PointType {
rv = [][][][]float64{{{extractCoordinates(coordValue)}}}
point := extractCoordinates(coordValue)

// ignore the contents with invalid entry.
if len(point) < 2 {
return nil, typ, false
}

rv = [][][][]float64{{{point}}}
return rv, typ, true
}

if typ == MultiPointType || typ == LineStringType ||
typ == EnvelopeType {
rv = [][][][]float64{{extract2DCoordinates(coordValue)}}
coords := extract2DCoordinates(coordValue)

// ignore the contents with invalid entry.
if len(coords) == 0 {
return nil, typ, false
}

if typ == EnvelopeType && len(coords) != 2 {
return nil, typ, false
}

if typ == LineStringType && len(coords) < 2 {
return nil, typ, false
}

rv = [][][][]float64{{coords}}
return rv, typ, true
}

if typ == PolygonType || typ == MultiLineStringType {
rv = [][][][]float64{extract3DCoordinates(coordValue)}
coords := extract3DCoordinates(coordValue)

// ignore the contents with invalid entry.
if len(coords) == 0 {
return nil, typ, false
}

if typ == PolygonType && len(coords[0]) < 3 ||
typ == MultiLineStringType && len(coords[0]) < 2 {
return nil, typ, false
}

rv = [][][][]float64{coords}
return rv, typ, true
}

if typ == MultiPolygonType {
rv = extract4DCoordinates(coordValue)

// ignore the contents with invalid entry.
if len(rv) == 0 || len(rv[0]) == 0 {
return nil, typ, false

}

if len(rv[0][0]) < 3 {
return nil, typ, false
}

return rv, typ, true
}

Expand Down
156 changes: 155 additions & 1 deletion geo/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@

package geo

import "testing"
import (
"reflect"
"testing"
)

func TestExtractGeoPoint(t *testing.T) {

Expand Down Expand Up @@ -199,3 +202,154 @@ func (s *s12) Lng() float64 {
func (s *s12) Lat() float64 {
return s.lat
}

func TestExtractGeoShape(t *testing.T) {
tests := []struct {
in interface{}
resTyp string
result [][][][]float64
success bool
}{
// valid point slice
{
in: map[string]interface{}{
"coordinates": []interface{}{3.4, 5.9},
"type": "Point",
},
resTyp: "point",
result: [][][][]float64{{{{3.4, 5.9}}}},
success: true,
},
// invalid point slice
{
in: map[string]interface{}{
"coordinates": []interface{}{3.4},
"type": "point"},

resTyp: "point",
result: nil,
success: false,
},
// valid multipoint slice containing single point
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 5.9}},
"type": "multipoint"},
resTyp: "multipoint",
result: [][][][]float64{{{{3.4, 5.9}}}},
success: true,
},
// valid multipoint slice
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 5.9}, {6.7, 9.8}},
"type": "multipoint"},
resTyp: "multipoint",
result: [][][][]float64{{{{3.4, 5.9}, {6.7, 9.8}}}},
success: true,
},
// valid multipoint slice containing one invalid entry
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 5.9}, {6.7}},
"type": "multipoint"},
resTyp: "multipoint",
result: [][][][]float64{{{{3.4, 5.9}}}},
success: true,
},
// invalid multipoint slice
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4}},
"type": "multipoint"},
resTyp: "multipoint",
result: nil,
success: false,
},
// valid linestring slice
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 4.4}, {8.4, 9.4}},
"type": "linestring"},
resTyp: "linestring",
result: [][][][]float64{{{{3.4, 4.4}, {8.4, 9.4}}}},
success: true,
},
// valid linestring slice
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 4.4}, {8.4, 9.4}, {10.1, 12.3}},
"type": "linestring"},
resTyp: "linestring",
result: [][][][]float64{{{{3.4, 4.4}, {8.4, 9.4}, {10.1, 12.3}}}},
success: true,
},
// invalid linestring slice with single entry
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 4.4}},
"type": "linestring"},
resTyp: "linestring",
result: nil,
success: false,
},
// invalid linestring slice with wrong paranthesis
{
in: map[string]interface{}{
"coordinates": [][][]interface{}{{{3.4, 4.4}, {8.4, 9.4}}},
"type": "linestring"},
resTyp: "linestring",
result: nil,
success: false,
},
// valid envelope
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 4.4}, {8.4, 9.4}},
"type": "envelope"},
resTyp: "envelope",
result: [][][][]float64{{{{3.4, 4.4}, {8.4, 9.4}}}},
success: true,
},
// invalid envelope
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 4.4}},
"type": "envelope"},
resTyp: "envelope",
result: nil,
success: false,
},
// invalid envelope
{
in: map[string]interface{}{
"coordinates": [][][]interface{}{{{3.4, 4.4}, {8.4, 9.4}}},
"type": "envelope"},
resTyp: "envelope",
result: nil,
success: false,
},
// invalid envelope with >2 vertices
{
in: map[string]interface{}{
"coordinates": [][]interface{}{{3.4, 4.4}, {5.6, 6.4}, {7.4, 7.4}},
"type": "envelope"},
resTyp: "envelope",
result: nil,
success: false,
},
}

for _, test := range tests {
result, shapeType, success := extractGeoShape(test.in)
if success != test.success {
t.Errorf("expected extract geo point: %t, got: %t for: %v", test.success, success, test.in)
}
if shapeType != test.resTyp {
t.Errorf("expected shape type: %v, got: %v for input: %v", test.resTyp, shapeType, test.in)
}
if !reflect.DeepEqual(test.result, result) {
t.Errorf("expected result %+v, got %+v for %v", test.result, result, test.in)
}
}
}
31 changes: 1 addition & 30 deletions mapping/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import (
"encoding/json"
"fmt"
"net"
"reflect"
"strings"
"time"

"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
Expand Down Expand Up @@ -315,36 +313,9 @@ func (fm *FieldMapping) processIP(ip net.IP, pathString string, path []string, i
}
}

func parseGeoShapeField(thing interface{}) (interface{}, string, error) {
thingVal := reflect.ValueOf(thing)
if !thingVal.IsValid() {
return nil, "", nil
}

var shape string
var coordValue interface{}

if thingVal.Kind() == reflect.Map {
iter := thingVal.MapRange()
for iter.Next() {
if iter.Key().String() == "type" {
shape = iter.Value().Interface().(string)
continue

}

if iter.Key().String() == "coordinates" {
coordValue = iter.Value().Interface()
}
}
}

return coordValue, strings.ToLower(shape), nil
}

func (fm *FieldMapping) processGeoShape(propertyMightBeGeoShape interface{},
pathString string, path []string, indexes []uint64, context *walkContext) {
coordValue, shape, err := parseGeoShapeField(propertyMightBeGeoShape)
coordValue, shape, err := geo.ParseGeoShapeField(propertyMightBeGeoShape)
if err != nil {
return
}
Expand Down

0 comments on commit dee0c77

Please sign in to comment.