Skip to content

Commit 5c8f222

Browse files
authored
Merge pull request #4 from SliceOSM/geojson-validation
Stricter validation of geometry inputs [#3]
2 parents 0536107 + 62a2685 commit 5c8f222

File tree

2 files changed

+107
-27
lines changed

2 files changed

+107
-27
lines changed

main.go

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"bytes"
66
_ "embed"
77
"encoding/json"
8+
"errors"
89
"flag"
910
"fmt"
1011
"github.com/getsentry/sentry-go"
@@ -251,36 +252,73 @@ func GetPixel(image image.Image, z int, x int, y int) float64 {
251252
}
252253
}
253254

255+
func parseInput(body io.Reader) (orb.Geometry, string, string, json.RawMessage, error) {
256+
decoder := json.NewDecoder(body)
257+
258+
var input Input
259+
err := decoder.Decode(&input)
260+
if err != nil {
261+
return nil, "", "", nil, errors.New("input GeoJSON is invalid")
262+
}
263+
264+
var geom orb.Geometry
265+
var sanitizedData json.RawMessage
266+
267+
if input.RegionType == "geojson" {
268+
geojsonGeom, err := geojson.UnmarshalGeometry(input.RegionData)
269+
if err != nil {
270+
return nil, "", "", nil, errors.New("input GeoJSON is invalid")
271+
}
272+
geom = geojsonGeom.Geometry()
273+
switch v := geom.(type) {
274+
case orb.Polygon:
275+
if len(v) == 0 {
276+
return nil, "", "", nil, errors.New("geom does not have enough rings")
277+
}
278+
for _, ring := range v {
279+
if len(ring) < 4 {
280+
return nil, "", "", nil, errors.New("ring does not have enough coordinates")
281+
}
282+
}
283+
case orb.MultiPolygon:
284+
if len(v) == 0 {
285+
return nil, "", "", nil, errors.New("geom does not have enough rings")
286+
}
287+
for _, polygon := range v {
288+
if len(polygon) == 0 {
289+
return nil, "", "", nil, errors.New("geom does not have enough rings")
290+
}
291+
for _, ring := range polygon {
292+
if len(ring) < 4 {
293+
return nil, "", "", nil, errors.New("ring does not have enough coordinates")
294+
}
295+
}
296+
}
297+
}
298+
sanitizedData, _ = geojsonGeom.MarshalJSON()
299+
} else if input.RegionType == "bbox" {
300+
var coords []float64
301+
json.Unmarshal(input.RegionData, &coords)
302+
if len(coords) < 4 {
303+
return nil, "", "", nil, errors.New("input does not have >3 coordinates")
304+
}
305+
geom = orb.MultiPoint{orb.Point{coords[1], coords[0]}, orb.Point{coords[3], coords[2]}}.Bound()
306+
sanitizedData, _ = json.Marshal(coords[0:4])
307+
} else {
308+
return nil, "", "", nil, errors.New("invalid input RegionType")
309+
}
310+
311+
return geom, input.Name, input.RegionType, sanitizedData, nil
312+
}
313+
254314
// check the filesystem for the result JSON
255315
// if it's not started yet, return the position in the queue
256316
func (h *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
257317
w.Header().Set("Access-Control-Allow-Origin", "*")
258318
if r.Method == "POST" {
259-
decoder := json.NewDecoder(r.Body)
319+
geom, sanitized_name, sanitized_type, sanitized_region, err := parseInput(r.Body)
260320

261-
var input Input
262-
err := decoder.Decode(&input)
263321
if err != nil {
264-
panic(err)
265-
}
266-
267-
var geom orb.Geometry
268-
var sanitizedData json.RawMessage
269-
// validate input
270-
if input.RegionType == "geojson" {
271-
geojsonGeom, _ := geojson.UnmarshalGeometry(input.RegionData)
272-
geom = geojsonGeom.Geometry()
273-
sanitizedData, _ = geojsonGeom.MarshalJSON()
274-
} else if input.RegionType == "bbox" {
275-
var coords []float64
276-
json.Unmarshal(input.RegionData, &coords)
277-
if len(coords) < 4 {
278-
w.WriteHeader(400)
279-
return
280-
}
281-
geom = orb.MultiPoint{orb.Point{coords[1], coords[0]}, orb.Point{coords[3], coords[2]}}.Bound()
282-
sanitizedData, _ = json.Marshal(coords[0:4])
283-
} else {
284322
w.WriteHeader(400)
285323
return
286324
}
@@ -291,10 +329,7 @@ func (h *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
291329
return
292330
}
293331

294-
// todo: sanitize name
295-
// end validate input
296-
297-
task := Task{Uuid: uuid.New().String(), SanitizedName: input.Name, SanitizedRegionType: input.RegionType, SanitizedRegionData: sanitizedData}
332+
task := Task{Uuid: uuid.New().String(), SanitizedName: sanitized_name, SanitizedRegionType: sanitized_type, SanitizedRegionData: sanitized_region}
298333

299334
select {
300335
case h.queue <- task:

main_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/stretchr/testify/assert"
55
"image/png"
66
"os"
7+
"strings"
78
"testing"
89
)
910

@@ -13,3 +14,47 @@ func TestGetPixel(t *testing.T) {
1314
img, _ := png.Decode(file)
1415
assert.Equal(t, 249.125, GetPixel(img, 14, 2620, 6331))
1516
}
17+
18+
func TestGeoJSON(t *testing.T) {
19+
_, name, regiontype, _, err := parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"geojson", "RegionData":{"type":"Polygon","coordinates":[[[0,0],[1,1],[1,0],[0,0]]]}}`))
20+
assert.Nil(t, err)
21+
assert.Equal(t, "a_name", name)
22+
assert.Equal(t, "geojson", regiontype)
23+
}
24+
25+
func TestBbox(t *testing.T) {
26+
_, name, regiontype, _, err := parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"bbox", "RegionData":[0,0,1,1]}`))
27+
assert.Nil(t, err)
28+
assert.Equal(t, "a_name", name)
29+
assert.Equal(t, "bbox", regiontype)
30+
}
31+
32+
func TestInvalidGeoJSON(t *testing.T) {
33+
_, _, _, _, err := parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"geojson", "RegionData":null}`))
34+
assert.NotNil(t, err)
35+
}
36+
37+
func TestMalformedGeoJSON(t *testing.T) {
38+
_, _, _, _, err := parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"geojson", "RegionData":[}`))
39+
assert.NotNil(t, err)
40+
}
41+
42+
func TestEmptyGeoJSONPolygon(t *testing.T) {
43+
_, _, _, _, err := parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"geojson", "RegionData":{"type":"Polygon","coordinates":[]}}`))
44+
assert.NotNil(t, err)
45+
}
46+
func TestEmptyGeoJSONMultiPolygon(t *testing.T) {
47+
_, _, _, _, err := parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"geojson", "RegionData":{"type":"MultiPolygon","coordinates":[]}}`))
48+
assert.NotNil(t, err)
49+
}
50+
51+
func TestGeoJSONPolygonTooFewCoords(t *testing.T) {
52+
_, _, _, _, err := parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"geojson", "RegionData":{"type":"Polygon","coordinates":[[[0,0],[1,1],[0,0]]]}}`))
53+
assert.NotNil(t, err)
54+
}
55+
func TestGeoJSONMultiPolygonTooFewCoords(t *testing.T) {
56+
_, _, _, _, err := parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"geojson", "RegionData":{"type":"MultiPolygon","coordinates":[[]]}}`))
57+
assert.NotNil(t, err)
58+
_, _, _, _, err = parseInput(strings.NewReader(`{"Name":"a_name", "RegionType":"geojson", "RegionData":{"type":"MultiPolygon","coordinates":[[[],[]]]}}`))
59+
assert.NotNil(t, err)
60+
}

0 commit comments

Comments
 (0)