Skip to content

Commit a8f69f4

Browse files
fix: detects circular references that can't be handled at the moment to avoid infinite loops loading documents (#607)
1 parent 6610338 commit a8f69f4

File tree

5 files changed

+269
-19
lines changed

5 files changed

+269
-19
lines changed

openapi3/issue542_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package openapi3
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestIssue542(t *testing.T) {
10+
sl := NewLoader()
11+
12+
_, err := sl.LoadFromFile("testdata/issue542.yml")
13+
require.Error(t, err)
14+
require.Contains(t, err.Error(), CircularReferenceError)
15+
}

openapi3/issue570_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package openapi3
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestIssue570(t *testing.T) {
11+
loader := NewLoader()
12+
_, err := loader.LoadFromFile("testdata/issue570.json")
13+
require.Error(t, err)
14+
assert.Contains(t, err.Error(), CircularReferenceError)
15+
}

openapi3/loader.go

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"github.com/invopop/yaml"
1616
)
1717

18+
var CircularReferenceError = "kin-openapi bug found: circular schema reference not handled"
19+
1820
func foundUnresolvedRef(ref string) error {
1921
return fmt.Errorf("found unresolved ref: %q", ref)
2022
}
@@ -197,7 +199,7 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) {
197199
}
198200
}
199201
for _, component := range components.Schemas {
200-
if err = loader.resolveSchemaRef(doc, component, location); err != nil {
202+
if err = loader.resolveSchemaRef(doc, component, location, []string{}); err != nil {
201203
return
202204
}
203205
}
@@ -480,7 +482,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat
480482
}
481483

482484
if schema := value.Schema; schema != nil {
483-
if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil {
485+
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
484486
return err
485487
}
486488
}
@@ -532,13 +534,13 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum
532534
}
533535
for _, contentType := range value.Content {
534536
if schema := contentType.Schema; schema != nil {
535-
if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil {
537+
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
536538
return err
537539
}
538540
}
539541
}
540542
if schema := value.Schema; schema != nil {
541-
if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil {
543+
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
542544
return err
543545
}
544546
}
@@ -592,7 +594,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d
592594
contentType.Examples[name] = example
593595
}
594596
if schema := contentType.Schema; schema != nil {
595-
if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil {
597+
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
596598
return err
597599
}
598600
}
@@ -656,7 +658,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
656658
contentType.Examples[name] = example
657659
}
658660
if schema := contentType.Schema; schema != nil {
659-
if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil {
661+
if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil {
660662
return err
661663
}
662664
contentType.Schema = schema
@@ -670,8 +672,12 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
670672
return nil
671673
}
672674

673-
func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPath *url.URL) (err error) {
674-
if component != nil && component.Value != nil {
675+
func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPath *url.URL, visited []string) (err error) {
676+
if component == nil {
677+
return errors.New("invalid schema: value MUST be an object")
678+
}
679+
680+
if component.Value != nil {
675681
if loader.visitedSchema == nil {
676682
loader.visitedSchema = make(map[*Schema]struct{})
677683
}
@@ -681,9 +687,6 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
681687
loader.visitedSchema[component.Value] = struct{}{}
682688
}
683689

684-
if component == nil {
685-
return errors.New("invalid schema: value MUST be an object")
686-
}
687690
ref := component.Ref
688691
if ref != "" {
689692
if isSingleRefElement(ref) {
@@ -693,12 +696,18 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
693696
}
694697
component.Value = &schema
695698
} else {
699+
if visitedLimit(visited, ref, 3) {
700+
visited = append(visited, ref)
701+
return fmt.Errorf("%s - %s", CircularReferenceError, strings.Join(visited, " -> "))
702+
}
703+
visited = append(visited, ref)
704+
696705
var resolved SchemaRef
697706
componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
698707
if err != nil {
699708
return err
700709
}
701-
if err := loader.resolveSchemaRef(doc, &resolved, componentPath); err != nil {
710+
if err := loader.resolveSchemaRef(doc, &resolved, componentPath, visited); err != nil {
702711
return err
703712
}
704713
component.Value = resolved.Value
@@ -713,37 +722,37 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
713722

714723
// ResolveRefs referred schemas
715724
if v := value.Items; v != nil {
716-
if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil {
725+
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
717726
return err
718727
}
719728
}
720729
for _, v := range value.Properties {
721-
if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil {
730+
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
722731
return err
723732
}
724733
}
725734
if v := value.AdditionalProperties; v != nil {
726-
if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil {
735+
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
727736
return err
728737
}
729738
}
730739
if v := value.Not; v != nil {
731-
if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil {
740+
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
732741
return err
733742
}
734743
}
735744
for _, v := range value.AllOf {
736-
if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil {
745+
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
737746
return err
738747
}
739748
}
740749
for _, v := range value.AnyOf {
741-
if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil {
750+
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
742751
return err
743752
}
744753
}
745754
for _, v := range value.OneOf {
746-
if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil {
755+
if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
747756
return err
748757
}
749758
}
@@ -1046,3 +1055,16 @@ func (loader *Loader) resolvePathItemRefContinued(doc *T, pathItem *PathItem, do
10461055
func unescapeRefString(ref string) string {
10471056
return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1)
10481057
}
1058+
1059+
func visitedLimit(visited []string, ref string, limit int) bool {
1060+
visitedCount := 0
1061+
for _, v := range visited {
1062+
if v == ref {
1063+
visitedCount++
1064+
if visitedCount >= limit {
1065+
return true
1066+
}
1067+
}
1068+
}
1069+
return false
1070+
}

openapi3/testdata/issue542.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
openapi: "3.0.0"
2+
info:
3+
version: 1.0.0
4+
title: Swagger Petstore
5+
license:
6+
name: MIT
7+
servers:
8+
- url: http://petstore.swagger.io/v1
9+
paths: {}
10+
#paths:
11+
# /pets:
12+
# patch:
13+
# requestBody:
14+
# content:
15+
# application/json:
16+
# schema:
17+
# oneOf:
18+
# - $ref: '#/components/schemas/Cat'
19+
# - $ref: '#/components/schemas/Kitten'
20+
# discriminator:
21+
# propertyName: pet_type
22+
# responses:
23+
# '200':
24+
# description: Updated
25+
components:
26+
schemas:
27+
Cat:
28+
anyOf:
29+
- $ref: "#/components/schemas/Kitten"
30+
- type: object
31+
# properties:
32+
# hunts:
33+
# type: boolean
34+
# age:
35+
# type: integer
36+
# offspring:
37+
Kitten:
38+
$ref: "#/components/schemas/Cat" #ko
39+
40+
# type: string #ok
41+
42+
# allOf: #ko
43+
# - $ref: '#/components/schemas/Cat'

openapi3/testdata/issue570.json

Lines changed: 155 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)