mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 10:22:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			431 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 go-swagger maintainers
 | |
| //
 | |
| // 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 validate
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/go-openapi/errors"
 | |
| 	"github.com/go-openapi/spec"
 | |
| 	"github.com/go-openapi/strfmt"
 | |
| )
 | |
| 
 | |
| type objectValidator struct {
 | |
| 	Path                 string
 | |
| 	In                   string
 | |
| 	MaxProperties        *int64
 | |
| 	MinProperties        *int64
 | |
| 	Required             []string
 | |
| 	Properties           map[string]spec.Schema
 | |
| 	AdditionalProperties *spec.SchemaOrBool
 | |
| 	PatternProperties    map[string]spec.Schema
 | |
| 	Root                 interface{}
 | |
| 	KnownFormats         strfmt.Registry
 | |
| 	Options              *SchemaValidatorOptions
 | |
| 	splitPath            []string
 | |
| }
 | |
| 
 | |
| func newObjectValidator(path, in string,
 | |
| 	maxProperties, minProperties *int64, required []string, properties spec.SchemaProperties,
 | |
| 	additionalProperties *spec.SchemaOrBool, patternProperties spec.SchemaProperties,
 | |
| 	root interface{}, formats strfmt.Registry, opts *SchemaValidatorOptions) *objectValidator {
 | |
| 	if opts == nil {
 | |
| 		opts = new(SchemaValidatorOptions)
 | |
| 	}
 | |
| 
 | |
| 	var v *objectValidator
 | |
| 	if opts.recycleValidators {
 | |
| 		v = pools.poolOfObjectValidators.BorrowValidator()
 | |
| 	} else {
 | |
| 		v = new(objectValidator)
 | |
| 	}
 | |
| 
 | |
| 	v.Path = path
 | |
| 	v.In = in
 | |
| 	v.MaxProperties = maxProperties
 | |
| 	v.MinProperties = minProperties
 | |
| 	v.Required = required
 | |
| 	v.Properties = properties
 | |
| 	v.AdditionalProperties = additionalProperties
 | |
| 	v.PatternProperties = patternProperties
 | |
| 	v.Root = root
 | |
| 	v.KnownFormats = formats
 | |
| 	v.Options = opts
 | |
| 	v.splitPath = strings.Split(v.Path, ".")
 | |
| 
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) SetPath(path string) {
 | |
| 	o.Path = path
 | |
| 	o.splitPath = strings.Split(path, ".")
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool {
 | |
| 	// TODO: this should also work for structs
 | |
| 	// there is a problem in the type validator where it will be unhappy about null values
 | |
| 	// so that requires more testing
 | |
| 	_, isSchema := source.(*spec.Schema)
 | |
| 	return isSchema && (kind == reflect.Map || kind == reflect.Struct)
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) isProperties() bool {
 | |
| 	p := o.splitPath
 | |
| 	return len(p) > 1 && p[len(p)-1] == jsonProperties && p[len(p)-2] != jsonProperties
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) isDefault() bool {
 | |
| 	p := o.splitPath
 | |
| 	return len(p) > 1 && p[len(p)-1] == jsonDefault && p[len(p)-2] != jsonDefault
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) isExample() bool {
 | |
| 	p := o.splitPath
 | |
| 	return len(p) > 1 && (p[len(p)-1] == swaggerExample || p[len(p)-1] == swaggerExamples) && p[len(p)-2] != swaggerExample
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]interface{}) {
 | |
| 	// for swagger 2.0 schemas, there is an additional constraint to have array items defined explicitly.
 | |
| 	// with pure jsonschema draft 4, one may have arrays with undefined items (i.e. any type).
 | |
| 	if val == nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	t, typeFound := val[jsonType]
 | |
| 	if !typeFound {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	tpe, isString := t.(string)
 | |
| 	if !isString || tpe != arrayType {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	item, itemsKeyFound := val[jsonItems]
 | |
| 	if itemsKeyFound {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	res.AddErrors(errors.Required(jsonItems, o.Path, item))
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]interface{}) {
 | |
| 	if val == nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if o.isProperties() || o.isDefault() || o.isExample() {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	_, itemsKeyFound := val[jsonItems]
 | |
| 	if !itemsKeyFound {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	t, typeFound := val[jsonType]
 | |
| 	if !typeFound {
 | |
| 		// there is no type
 | |
| 		res.AddErrors(errors.Required(jsonType, o.Path, t))
 | |
| 	}
 | |
| 
 | |
| 	if tpe, isString := t.(string); !isString || tpe != arrayType {
 | |
| 		res.AddErrors(errors.InvalidType(o.Path, o.In, arrayType, nil))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) precheck(res *Result, val map[string]interface{}) {
 | |
| 	if o.Options.EnableArrayMustHaveItemsCheck {
 | |
| 		o.checkArrayMustHaveItems(res, val)
 | |
| 	}
 | |
| 	if o.Options.EnableObjectArrayTypeCheck {
 | |
| 		o.checkItemsMustBeTypeArray(res, val)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) Validate(data interface{}) *Result {
 | |
| 	if o.Options.recycleValidators {
 | |
| 		defer func() {
 | |
| 			o.redeem()
 | |
| 		}()
 | |
| 	}
 | |
| 
 | |
| 	var val map[string]interface{}
 | |
| 	if data != nil {
 | |
| 		var ok bool
 | |
| 		val, ok = data.(map[string]interface{})
 | |
| 		if !ok {
 | |
| 			return errorHelp.sErr(invalidObjectMsg(o.Path, o.In), o.Options.recycleResult)
 | |
| 		}
 | |
| 	}
 | |
| 	numKeys := int64(len(val))
 | |
| 
 | |
| 	if o.MinProperties != nil && numKeys < *o.MinProperties {
 | |
| 		return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties), o.Options.recycleResult)
 | |
| 	}
 | |
| 	if o.MaxProperties != nil && numKeys > *o.MaxProperties {
 | |
| 		return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties), o.Options.recycleResult)
 | |
| 	}
 | |
| 
 | |
| 	var res *Result
 | |
| 	if o.Options.recycleResult {
 | |
| 		res = pools.poolOfResults.BorrowResult()
 | |
| 	} else {
 | |
| 		res = new(Result)
 | |
| 	}
 | |
| 
 | |
| 	o.precheck(res, val)
 | |
| 
 | |
| 	// check validity of field names
 | |
| 	if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows {
 | |
| 		// Case: additionalProperties: false
 | |
| 		o.validateNoAdditionalProperties(val, res)
 | |
| 	} else {
 | |
| 		// Cases: empty additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> }
 | |
| 		o.validateAdditionalProperties(val, res)
 | |
| 	}
 | |
| 
 | |
| 	o.validatePropertiesSchema(val, res)
 | |
| 
 | |
| 	// Check patternProperties
 | |
| 	// TODO: it looks like we have done that twice in many cases
 | |
| 	for key, value := range val {
 | |
| 		_, regularProperty := o.Properties[key]
 | |
| 		matched, _, patterns := o.validatePatternProperty(key, value, res) // applies to regular properties as well
 | |
| 		if regularProperty || !matched {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		for _, pName := range patterns {
 | |
| 			if v, ok := o.PatternProperties[pName]; ok {
 | |
| 				r := newSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options).Validate(value)
 | |
| 				res.mergeForField(data.(map[string]interface{}), key, r)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) validateNoAdditionalProperties(val map[string]interface{}, res *Result) {
 | |
| 	for k := range val {
 | |
| 		if k == "$schema" || k == "id" {
 | |
| 			// special properties "$schema" and "id" are ignored
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		_, regularProperty := o.Properties[k]
 | |
| 		if regularProperty {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		matched := false
 | |
| 		for pk := range o.PatternProperties {
 | |
| 			re, err := compileRegexp(pk)
 | |
| 			if err != nil {
 | |
| 				continue
 | |
| 			}
 | |
| 			if matches := re.MatchString(k); matches {
 | |
| 				matched = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if matched {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k))
 | |
| 
 | |
| 		// BUG(fredbi): This section should move to a part dedicated to spec validation as
 | |
| 		// it will conflict with regular schemas where a property "headers" is defined.
 | |
| 
 | |
| 		//
 | |
| 		// Croaks a more explicit message on top of the standard one
 | |
| 		// on some recognized cases.
 | |
| 		//
 | |
| 		// NOTE: edge cases with invalid type assertion are simply ignored here.
 | |
| 		// NOTE: prefix your messages here by "IMPORTANT!" so there are not filtered
 | |
| 		// by higher level callers (the IMPORTANT! tag will be eventually
 | |
| 		// removed).
 | |
| 		if k != "headers" || val[k] == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// $ref is forbidden in header
 | |
| 		headers, mapOk := val[k].(map[string]interface{})
 | |
| 		if !mapOk {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		for headerKey, headerBody := range headers {
 | |
| 			if headerBody == nil {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			headerSchema, mapOfMapOk := headerBody.(map[string]interface{})
 | |
| 			if !mapOfMapOk {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			_, found := headerSchema["$ref"]
 | |
| 			if !found {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			refString, stringOk := headerSchema["$ref"].(string)
 | |
| 			if !stringOk {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			msg := strings.Join([]string{", one may not use $ref=\":", refString, "\""}, "")
 | |
| 			res.AddErrors(refNotAllowedInHeaderMsg(o.Path, headerKey, msg))
 | |
| 			/*
 | |
| 				case "$ref":
 | |
| 					if val[k] != nil {
 | |
| 						// TODO: check context of that ref: warn about siblings, check against invalid context
 | |
| 					}
 | |
| 			*/
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) validateAdditionalProperties(val map[string]interface{}, res *Result) {
 | |
| 	for key, value := range val {
 | |
| 		_, regularProperty := o.Properties[key]
 | |
| 		if regularProperty {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Validates property against "patternProperties" if applicable
 | |
| 		// BUG(fredbi): succeededOnce is always false
 | |
| 
 | |
| 		// NOTE: how about regular properties which do not match patternProperties?
 | |
| 		matched, succeededOnce, _ := o.validatePatternProperty(key, value, res)
 | |
| 		if matched || succeededOnce {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if o.AdditionalProperties == nil || o.AdditionalProperties.Schema == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Cases: properties which are not regular properties and have not been matched by the PatternProperties validator
 | |
| 		// AdditionalProperties as Schema
 | |
| 		r := newSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options).Validate(value)
 | |
| 		res.mergeForField(val, key, r)
 | |
| 	}
 | |
| 	// Valid cases: additionalProperties: true or undefined
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) validatePropertiesSchema(val map[string]interface{}, res *Result) {
 | |
| 	createdFromDefaults := map[string]struct{}{}
 | |
| 
 | |
| 	// Property types:
 | |
| 	// - regular Property
 | |
| 	pSchema := pools.poolOfSchemas.BorrowSchema() // recycle a spec.Schema object which lifespan extends only to the validation of properties
 | |
| 	defer func() {
 | |
| 		pools.poolOfSchemas.RedeemSchema(pSchema)
 | |
| 	}()
 | |
| 
 | |
| 	for pName := range o.Properties {
 | |
| 		*pSchema = o.Properties[pName]
 | |
| 		var rName string
 | |
| 		if o.Path == "" {
 | |
| 			rName = pName
 | |
| 		} else {
 | |
| 			rName = o.Path + "." + pName
 | |
| 		}
 | |
| 
 | |
| 		// Recursively validates each property against its schema
 | |
| 		v, ok := val[pName]
 | |
| 		if ok {
 | |
| 			r := newSchemaValidator(pSchema, o.Root, rName, o.KnownFormats, o.Options).Validate(v)
 | |
| 			res.mergeForField(val, pName, r)
 | |
| 
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if pSchema.Default != nil {
 | |
| 			// if a default value is defined, creates the property from defaults
 | |
| 			// NOTE: JSON schema does not enforce default values to be valid against schema. Swagger does.
 | |
| 			createdFromDefaults[pName] = struct{}{}
 | |
| 			if !o.Options.skipSchemataResult {
 | |
| 				res.addPropertySchemata(val, pName, pSchema) // this shallow-clones the content of the pSchema pointer
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(o.Required) == 0 {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Check required properties
 | |
| 	for _, k := range o.Required {
 | |
| 		v, ok := val[k]
 | |
| 		if ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		_, isCreatedFromDefaults := createdFromDefaults[k]
 | |
| 		if isCreatedFromDefaults {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		res.AddErrors(errors.Required(fmt.Sprintf("%s.%s", o.Path, k), o.In, v))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TODO: succeededOnce is not used anywhere
 | |
| func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) {
 | |
| 	if len(o.PatternProperties) == 0 {
 | |
| 		return false, false, nil
 | |
| 	}
 | |
| 
 | |
| 	matched := false
 | |
| 	succeededOnce := false
 | |
| 	patterns := make([]string, 0, len(o.PatternProperties))
 | |
| 
 | |
| 	schema := pools.poolOfSchemas.BorrowSchema()
 | |
| 	defer func() {
 | |
| 		pools.poolOfSchemas.RedeemSchema(schema)
 | |
| 	}()
 | |
| 
 | |
| 	for k := range o.PatternProperties {
 | |
| 		re, err := compileRegexp(k)
 | |
| 		if err != nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		match := re.MatchString(key)
 | |
| 		if !match {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		*schema = o.PatternProperties[k]
 | |
| 		patterns = append(patterns, k)
 | |
| 		matched = true
 | |
| 		validator := newSchemaValidator(schema, o.Root, fmt.Sprintf("%s.%s", o.Path, key), o.KnownFormats, o.Options)
 | |
| 
 | |
| 		res := validator.Validate(value)
 | |
| 		result.Merge(res)
 | |
| 	}
 | |
| 
 | |
| 	return matched, succeededOnce, patterns
 | |
| }
 | |
| 
 | |
| func (o *objectValidator) redeem() {
 | |
| 	pools.poolOfObjectValidators.RedeemValidator(o)
 | |
| }
 |