debian-forge-composer/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go
Sanne Raymaekers b2700903ae go.mod: bump github.com/getkin/kin-openapi to v0.131.0
As deepmap/oapi-codegen didn't work with this newer version, upgrade to
oapi-codegen/oapi-codegen v2.

Mitigating CVE-2025-30153
2025-03-26 11:13:14 +01:00

261 lines
7.4 KiB
Go

package openapi3
import (
"fmt"
"net/url"
"path"
"reflect"
"regexp"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
)
const identifierChars = `a-zA-Z0-9._-`
// IdentifierRegExp verifies whether Component object key matches contains just 'identifierChars', according to OpenAPI v3.x.
// InvalidIdentifierCharRegExp matches all characters not contained in 'identifierChars'.
// However, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in order not to fail
// converted v2-v3 validation
var (
IdentifierRegExp = regexp.MustCompile(`^[` + identifierChars + `]+$`)
InvalidIdentifierCharRegExp = regexp.MustCompile(`[^` + identifierChars + `]`)
)
// ValidateIdentifier returns an error if the given component name does not match [IdentifierRegExp].
func ValidateIdentifier(value string) error {
if IdentifierRegExp.MatchString(value) {
return nil
}
return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (charset: [%q])", value, identifierChars)
}
// Float64Ptr is a helper for defining OpenAPI schemas.
func Float64Ptr(value float64) *float64 {
return &value
}
// BoolPtr is a helper for defining OpenAPI schemas.
func BoolPtr(value bool) *bool {
return &value
}
// Int64Ptr is a helper for defining OpenAPI schemas.
func Int64Ptr(value int64) *int64 {
return &value
}
// Uint64Ptr is a helper for defining OpenAPI schemas.
func Uint64Ptr(value uint64) *uint64 {
return &value
}
// componentNames returns the map keys in a sorted slice.
func componentNames[E any](s map[string]E) []string {
out := make([]string, 0, len(s))
for i := range s {
out = append(out, i)
}
sort.Strings(out)
return out
}
// copyURI makes a copy of the pointer.
func copyURI(u *url.URL) *url.URL {
if u == nil {
return nil
}
c := *u // shallow-copy
return &c
}
type ComponentRef interface {
RefString() string
RefPath() *url.URL
CollectionName() string
}
// refersToSameDocument returns if the $ref refers to the same document.
//
// Documents in different directories will have distinct $ref values that resolve to
// the same document.
// For example, consider the 3 files:
//
// /records.yaml
// /root.yaml $ref: records.yaml
// /schema/other.yaml $ref: ../records.yaml
//
// The records.yaml reference in the 2 latter refers to the same document.
func refersToSameDocument(o1 ComponentRef, o2 ComponentRef) bool {
if o1 == nil || o2 == nil {
return false
}
r1 := o1.RefPath()
r2 := o2.RefPath()
if r1 == nil || r2 == nil {
return false
}
// refURL is relative to the working directory & base spec file.
return referenceURIMatch(r1, r2)
}
// referencesRootDocument returns if the $ref points to the root document of the OpenAPI spec.
//
// If the document has no location, perhaps loaded from data in memory, it always returns false.
func referencesRootDocument(doc *T, ref ComponentRef) bool {
if doc.url == nil || ref == nil || ref.RefPath() == nil {
return false
}
refURL := *ref.RefPath()
refURL.Fragment = ""
// Check referenced element was in the root document.
return referenceURIMatch(doc.url, &refURL)
}
func referenceURIMatch(u1 *url.URL, u2 *url.URL) bool {
s1, s2 := *u1, *u2
if s1.Scheme == "" {
s1.Scheme = "file"
}
if s2.Scheme == "" {
s2.Scheme = "file"
}
return s1.String() == s2.String()
}
// ReferencesComponentInRootDocument returns if the given component reference references
// the same document or element as another component reference in the root document's
// '#/components/<type>'. If it does, it returns the name of it in the form
// '#/components/<type>/NameXXX'
//
// Of course given a component from the root document will always match itself.
//
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#relative-references-in-urls
//
// Example. Take the spec with directory structure:
//
// openapi.yaml
// schemas/
// ├─ record.yaml
// ├─ records.yaml
//
// In openapi.yaml we have:
//
// components:
// schemas:
// Record:
// $ref: schemas/record.yaml
//
// Case 1: records.yml references a component in the root document
//
// $ref: ../openapi.yaml#/components/schemas/Record
//
// This would return...
//
// #/components/schemas/Record
//
// Case 2: records.yml indirectly refers to the same schema
// as a schema the root document's '#/components/schemas'.
//
// $ref: ./record.yaml
//
// This would also return...
//
// #/components/schemas/Record
func ReferencesComponentInRootDocument(doc *T, ref ComponentRef) (string, bool) {
if ref == nil || ref.RefString() == "" {
return "", false
}
// Case 1:
// Something like: ../another-folder/document.json#/myElement
if isRemoteReference(ref.RefString()) && isRootComponentReference(ref.RefString(), ref.CollectionName()) {
// Determine if it is *this* root doc.
if referencesRootDocument(doc, ref) {
_, name, _ := strings.Cut(ref.RefString(), path.Join("#/components/", ref.CollectionName()))
return path.Join("#/components/", ref.CollectionName(), name), true
}
}
// If there are no schemas defined in the root document return early.
if doc.Components == nil {
return "", false
}
collection, _, err := jsonpointer.GetForToken(doc.Components, ref.CollectionName())
if err != nil {
panic(err) // unreachable
}
var components map[string]ComponentRef
componentRefType := reflect.TypeOf(new(ComponentRef)).Elem()
if t := reflect.TypeOf(collection); t.Kind() == reflect.Map &&
t.Key().Kind() == reflect.String &&
t.Elem().AssignableTo(componentRefType) {
v := reflect.ValueOf(collection)
components = make(map[string]ComponentRef, v.Len())
for _, key := range v.MapKeys() {
strct := v.MapIndex(key)
// Type assertion safe, already checked via reflection above.
components[key.Interface().(string)] = strct.Interface().(ComponentRef)
}
} else {
return "", false
}
// Case 2:
// Something like: ../openapi.yaml#/components/schemas/myElement
for name, s := range components {
// Must be a reference to a YAML file.
if !isWholeDocumentReference(s.RefString()) {
continue
}
// Is the schema a ref to the same resource.
if !refersToSameDocument(s, ref) {
continue
}
// Transform the remote ref to the equivalent schema in the root document.
return path.Join("#/components/", ref.CollectionName(), name), true
}
return "", false
}
// isElementReference takes a $ref value and checks if it references a specific element.
func isElementReference(ref string) bool {
return ref != "" && !isWholeDocumentReference(ref)
}
// isSchemaReference takes a $ref value and checks if it references a schema element.
func isRootComponentReference(ref string, compType string) bool {
return isElementReference(ref) && strings.Contains(ref, path.Join("#/components/", compType))
}
// isWholeDocumentReference takes a $ref value and checks if it is whole document reference.
func isWholeDocumentReference(ref string) bool {
return ref != "" && !strings.ContainsAny(ref, "#")
}
// isRemoteReference takes a $ref value and checks if it is remote reference.
func isRemoteReference(ref string) bool {
return ref != "" && !strings.HasPrefix(ref, "#") && !isURLReference(ref)
}
// isURLReference takes a $ref value and checks if it is URL reference.
func isURLReference(ref string) bool {
return strings.HasPrefix(ref, "http://") || strings.HasPrefix(ref, "https://") || strings.HasPrefix(ref, "//")
}