From dd1001ceea3d09a252fda2a00fe5a366c3f50247 Mon Sep 17 00:00:00 2001 From: Tom Wieczorek Date: Sat, 27 May 2023 00:16:14 +0200 Subject: [PATCH] fix: Prevent panic when parsing empty documents (#389) The ParseDocument functions were returning the first element of the content slice unconditionally, which panicked if the given document bytes were empty. Check if the slice has some elements and return an error if it's empty instead. --- discovery/discovery_test.go | 25 ++++++++++++++++++++++ discovery/document.go | 7 +++++++ openapiv2/document.go | 7 +++++++ openapiv2/document_test.go | 42 +++++++++++++++++++++++++++++++++++++ openapiv3/document.go | 7 +++++++ openapiv3/openapiv3_test.go | 25 ++++++++++++++++++++++ 6 files changed, 113 insertions(+) create mode 100644 openapiv2/document_test.go diff --git a/discovery/discovery_test.go b/discovery/discovery_test.go index f597c837..aebc6f5b 100644 --- a/discovery/discovery_test.go +++ b/discovery/discovery_test.go @@ -46,3 +46,28 @@ func TestParseDocument(t *testing.T) { t.Errorf("unexpected value for Title: %s (expected %s)", d.Title, title) } } + +func TestParseDocument_Empty(t *testing.T) { + for _, test := range []struct { + name string + data []byte + }{ + {"nil", nil}, + {"zero_bytes", []byte{}}, + {"whitespace", []byte(" ")}, + } { + t.Run(test.name, func(t *testing.T) { + d, err := ParseDocument(test.data) + + t.Log(err) + if err == nil { + t.Error("expected error") + } else if want, got := "document has no content", err.Error(); want != got { + t.Errorf("unexpected error: %q (expected %q)", got, want) + } + if d != nil { + t.Error("expected document to be nil") + } + }) + } +} diff --git a/discovery/document.go b/discovery/document.go index 13316d1b..2745d94c 100644 --- a/discovery/document.go +++ b/discovery/document.go @@ -15,6 +15,8 @@ package discovery_v1 import ( + "errors" + "github.com/google/gnostic/compiler" ) @@ -29,6 +31,11 @@ func ParseDocument(b []byte) (*Document, error) { if err != nil { return nil, err } + + if len(info.Content) < 1 { + return nil, errors.New("document has no content") + } + root := info.Content[0] return NewDocument(root, compiler.NewContext("$root", root, nil)) } diff --git a/openapiv2/document.go b/openapiv2/document.go index 0021ae87..98d67135 100644 --- a/openapiv2/document.go +++ b/openapiv2/document.go @@ -15,6 +15,8 @@ package openapi_v2 import ( + "errors" + "gopkg.in/yaml.v3" "github.com/google/gnostic/compiler" @@ -26,6 +28,11 @@ func ParseDocument(b []byte) (*Document, error) { if err != nil { return nil, err } + + if len(info.Content) < 1 { + return nil, errors.New("document has no content") + } + root := info.Content[0] return NewDocument(root, compiler.NewContextWithExtensions("$root", root, nil, nil)) } diff --git a/openapiv2/document_test.go b/openapiv2/document_test.go new file mode 100644 index 00000000..1a26a8ec --- /dev/null +++ b/openapiv2/document_test.go @@ -0,0 +1,42 @@ +// Copyright 2023 Google LLC. All Rights Reserved. +// +// Licensed 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 openapi_v2 + +import "testing" + +func TestParseDocument_Empty(t *testing.T) { + for _, test := range []struct { + name string + data []byte + }{ + {"nil", nil}, + {"zero_bytes", []byte{}}, + {"whitespace", []byte(" ")}, + } { + t.Run(test.name, func(t *testing.T) { + d, err := ParseDocument(test.data) + + t.Log(err) + if err == nil { + t.Error("expected error") + } else if want, got := "document has no content", err.Error(); want != got { + t.Errorf("unexpected error: %q (expected %q)", got, want) + } + if d != nil { + t.Error("expected document to be nil") + } + }) + } +} diff --git a/openapiv3/document.go b/openapiv3/document.go index ef10d1d9..26b2d948 100644 --- a/openapiv3/document.go +++ b/openapiv3/document.go @@ -15,6 +15,8 @@ package openapi_v3 import ( + "errors" + "gopkg.in/yaml.v3" "github.com/google/gnostic/compiler" @@ -26,6 +28,11 @@ func ParseDocument(b []byte) (*Document, error) { if err != nil { return nil, err } + + if len(info.Content) < 1 { + return nil, errors.New("document has no content") + } + root := info.Content[0] return NewDocument(root, compiler.NewContextWithExtensions("$root", root, nil, nil)) } diff --git a/openapiv3/openapiv3_test.go b/openapiv3/openapiv3_test.go index 4117c48e..621cab51 100644 --- a/openapiv3/openapiv3_test.go +++ b/openapiv3/openapiv3_test.go @@ -36,3 +36,28 @@ func TestParseDocument(t *testing.T) { t.Errorf("unexpected value for Title: %s (expected %s)", d.Info.Title, title) } } + +func TestParseDocument_Empty(t *testing.T) { + for _, test := range []struct { + name string + data []byte + }{ + {"nil", nil}, + {"zero_bytes", []byte{}}, + {"whitespace", []byte(" ")}, + } { + t.Run(test.name, func(t *testing.T) { + d, err := ParseDocument(test.data) + + t.Log(err) + if err == nil { + t.Error("expected error") + } else if want, got := "document has no content", err.Error(); want != got { + t.Errorf("unexpected error: %q (expected %q)", got, want) + } + if d != nil { + t.Error("expected document to be nil") + } + }) + } +}