mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-02 16:52:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			333 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
	
		
			10 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
 | 
						|
 | 
						|
// TODO: define this as package validate/internal
 | 
						|
// This must be done while keeping CI intact with all tests and test coverage
 | 
						|
 | 
						|
import (
 | 
						|
	"reflect"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/go-openapi/errors"
 | 
						|
	"github.com/go-openapi/spec"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	swaggerBody     = "body"
 | 
						|
	swaggerExample  = "example"
 | 
						|
	swaggerExamples = "examples"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	objectType  = "object"
 | 
						|
	arrayType   = "array"
 | 
						|
	stringType  = "string"
 | 
						|
	integerType = "integer"
 | 
						|
	numberType  = "number"
 | 
						|
	booleanType = "boolean"
 | 
						|
	fileType    = "file"
 | 
						|
	nullType    = "null"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	jsonProperties = "properties"
 | 
						|
	jsonItems      = "items"
 | 
						|
	jsonType       = "type"
 | 
						|
	// jsonSchema     = "schema"
 | 
						|
	jsonDefault = "default"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	stringFormatDate     = "date"
 | 
						|
	stringFormatDateTime = "date-time"
 | 
						|
	stringFormatPassword = "password"
 | 
						|
	stringFormatByte     = "byte"
 | 
						|
	// stringFormatBinary       = "binary"
 | 
						|
	stringFormatCreditCard   = "creditcard"
 | 
						|
	stringFormatDuration     = "duration"
 | 
						|
	stringFormatEmail        = "email"
 | 
						|
	stringFormatHexColor     = "hexcolor"
 | 
						|
	stringFormatHostname     = "hostname"
 | 
						|
	stringFormatIPv4         = "ipv4"
 | 
						|
	stringFormatIPv6         = "ipv6"
 | 
						|
	stringFormatISBN         = "isbn"
 | 
						|
	stringFormatISBN10       = "isbn10"
 | 
						|
	stringFormatISBN13       = "isbn13"
 | 
						|
	stringFormatMAC          = "mac"
 | 
						|
	stringFormatBSONObjectID = "bsonobjectid"
 | 
						|
	stringFormatRGBColor     = "rgbcolor"
 | 
						|
	stringFormatSSN          = "ssn"
 | 
						|
	stringFormatURI          = "uri"
 | 
						|
	stringFormatUUID         = "uuid"
 | 
						|
	stringFormatUUID3        = "uuid3"
 | 
						|
	stringFormatUUID4        = "uuid4"
 | 
						|
	stringFormatUUID5        = "uuid5"
 | 
						|
 | 
						|
	integerFormatInt32  = "int32"
 | 
						|
	integerFormatInt64  = "int64"
 | 
						|
	integerFormatUInt32 = "uint32"
 | 
						|
	integerFormatUInt64 = "uint64"
 | 
						|
 | 
						|
	numberFormatFloat32 = "float32"
 | 
						|
	numberFormatFloat64 = "float64"
 | 
						|
	numberFormatFloat   = "float"
 | 
						|
	numberFormatDouble  = "double"
 | 
						|
)
 | 
						|
 | 
						|
// Helpers available at the package level
 | 
						|
var (
 | 
						|
	pathHelp     *pathHelper
 | 
						|
	valueHelp    *valueHelper
 | 
						|
	errorHelp    *errorHelper
 | 
						|
	paramHelp    *paramHelper
 | 
						|
	responseHelp *responseHelper
 | 
						|
)
 | 
						|
 | 
						|
type errorHelper struct {
 | 
						|
	// A collection of unexported helpers for error construction
 | 
						|
}
 | 
						|
 | 
						|
func (h *errorHelper) sErr(err errors.Error, recycle bool) *Result {
 | 
						|
	// Builds a Result from standard errors.Error
 | 
						|
	var result *Result
 | 
						|
	if recycle {
 | 
						|
		result = pools.poolOfResults.BorrowResult()
 | 
						|
	} else {
 | 
						|
		result = new(Result)
 | 
						|
	}
 | 
						|
	result.Errors = []error{err}
 | 
						|
 | 
						|
	return result
 | 
						|
}
 | 
						|
 | 
						|
func (h *errorHelper) addPointerError(res *Result, err error, ref string, fromPath string) *Result {
 | 
						|
	// Provides more context on error messages
 | 
						|
	// reported by the jsoinpointer package by altering the passed Result
 | 
						|
	if err != nil {
 | 
						|
		res.AddErrors(cannotResolveRefMsg(fromPath, ref, err))
 | 
						|
	}
 | 
						|
	return res
 | 
						|
}
 | 
						|
 | 
						|
type pathHelper struct {
 | 
						|
	// A collection of unexported helpers for path validation
 | 
						|
}
 | 
						|
 | 
						|
func (h *pathHelper) stripParametersInPath(path string) string {
 | 
						|
	// Returns a path stripped from all path parameters, with multiple or trailing slashes removed.
 | 
						|
	//
 | 
						|
	// Stripping is performed on a slash-separated basis, e.g '/a{/b}' remains a{/b} and not /a.
 | 
						|
	//  - Trailing "/" make a difference, e.g. /a/ !~ /a (ex: canary/bitbucket.org/swagger.json)
 | 
						|
	//  - presence or absence of a parameter makes a difference, e.g. /a/{log} !~ /a/ (ex: canary/kubernetes/swagger.json)
 | 
						|
 | 
						|
	// Regexp to extract parameters from path, with surrounding {}.
 | 
						|
	// NOTE: important non-greedy modifier
 | 
						|
	rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
 | 
						|
	strippedSegments := []string{}
 | 
						|
 | 
						|
	for _, segment := range strings.Split(path, "/") {
 | 
						|
		strippedSegments = append(strippedSegments, rexParsePathParam.ReplaceAllString(segment, "X"))
 | 
						|
	}
 | 
						|
	return strings.Join(strippedSegments, "/")
 | 
						|
}
 | 
						|
 | 
						|
func (h *pathHelper) extractPathParams(path string) (params []string) {
 | 
						|
	// Extracts all params from a path, with surrounding "{}"
 | 
						|
	rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
 | 
						|
 | 
						|
	for _, segment := range strings.Split(path, "/") {
 | 
						|
		for _, v := range rexParsePathParam.FindAllStringSubmatch(segment, -1) {
 | 
						|
			params = append(params, v...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
type valueHelper struct {
 | 
						|
	// A collection of unexported helpers for value validation
 | 
						|
}
 | 
						|
 | 
						|
func (h *valueHelper) asInt64(val interface{}) int64 {
 | 
						|
	// Number conversion function for int64, without error checking
 | 
						|
	// (implements an implicit type upgrade).
 | 
						|
	v := reflect.ValueOf(val)
 | 
						|
	switch v.Kind() { //nolint:exhaustive
 | 
						|
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
						|
		return v.Int()
 | 
						|
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | 
						|
		return int64(v.Uint())
 | 
						|
	case reflect.Float32, reflect.Float64:
 | 
						|
		return int64(v.Float())
 | 
						|
	default:
 | 
						|
		// panic("Non numeric value in asInt64()")
 | 
						|
		return 0
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (h *valueHelper) asUint64(val interface{}) uint64 {
 | 
						|
	// Number conversion function for uint64, without error checking
 | 
						|
	// (implements an implicit type upgrade).
 | 
						|
	v := reflect.ValueOf(val)
 | 
						|
	switch v.Kind() { //nolint:exhaustive
 | 
						|
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
						|
		return uint64(v.Int())
 | 
						|
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | 
						|
		return v.Uint()
 | 
						|
	case reflect.Float32, reflect.Float64:
 | 
						|
		return uint64(v.Float())
 | 
						|
	default:
 | 
						|
		// panic("Non numeric value in asUint64()")
 | 
						|
		return 0
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Same for unsigned floats
 | 
						|
func (h *valueHelper) asFloat64(val interface{}) float64 {
 | 
						|
	// Number conversion function for float64, without error checking
 | 
						|
	// (implements an implicit type upgrade).
 | 
						|
	v := reflect.ValueOf(val)
 | 
						|
	switch v.Kind() { //nolint:exhaustive
 | 
						|
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
						|
		return float64(v.Int())
 | 
						|
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | 
						|
		return float64(v.Uint())
 | 
						|
	case reflect.Float32, reflect.Float64:
 | 
						|
		return v.Float()
 | 
						|
	default:
 | 
						|
		// panic("Non numeric value in asFloat64()")
 | 
						|
		return 0
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type paramHelper struct {
 | 
						|
	// A collection of unexported helpers for parameters resolution
 | 
						|
}
 | 
						|
 | 
						|
func (h *paramHelper) safeExpandedParamsFor(path, method, operationID string, res *Result, s *SpecValidator) (params []spec.Parameter) {
 | 
						|
	operation, ok := s.expandedAnalyzer().OperationFor(method, path)
 | 
						|
	if ok {
 | 
						|
		// expand parameters first if necessary
 | 
						|
		resolvedParams := []spec.Parameter{}
 | 
						|
		for _, ppr := range operation.Parameters {
 | 
						|
			resolvedParam, red := h.resolveParam(path, method, operationID, &ppr, s) //#nosec
 | 
						|
			res.Merge(red)
 | 
						|
			if resolvedParam != nil {
 | 
						|
				resolvedParams = append(resolvedParams, *resolvedParam)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// remove params with invalid expansion from Slice
 | 
						|
		operation.Parameters = resolvedParams
 | 
						|
 | 
						|
		for _, ppr := range s.expandedAnalyzer().SafeParamsFor(method, path,
 | 
						|
			func(_ spec.Parameter, err error) bool {
 | 
						|
				// since params have already been expanded, there are few causes for error
 | 
						|
				res.AddErrors(someParametersBrokenMsg(path, method, operationID))
 | 
						|
				// original error from analyzer
 | 
						|
				res.AddErrors(err)
 | 
						|
				return true
 | 
						|
			}) {
 | 
						|
			params = append(params, ppr)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func (h *paramHelper) resolveParam(path, method, operationID string, param *spec.Parameter, s *SpecValidator) (*spec.Parameter, *Result) {
 | 
						|
	// Ensure parameter is expanded
 | 
						|
	var err error
 | 
						|
	res := new(Result)
 | 
						|
	isRef := param.Ref.String() != ""
 | 
						|
	if s.spec.SpecFilePath() == "" {
 | 
						|
		err = spec.ExpandParameterWithRoot(param, s.spec.Spec(), nil)
 | 
						|
	} else {
 | 
						|
		err = spec.ExpandParameter(param, s.spec.SpecFilePath())
 | 
						|
 | 
						|
	}
 | 
						|
	if err != nil { // Safeguard
 | 
						|
		// NOTE: we may enter here when the whole parameter is an unresolved $ref
 | 
						|
		refPath := strings.Join([]string{"\"" + path + "\"", method}, ".")
 | 
						|
		errorHelp.addPointerError(res, err, param.Ref.String(), refPath)
 | 
						|
		return nil, res
 | 
						|
	}
 | 
						|
	res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, isRef))
 | 
						|
	return param, res
 | 
						|
}
 | 
						|
 | 
						|
func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation string, isRef bool) *Result {
 | 
						|
	// Secure parameter structure after $ref resolution
 | 
						|
	res := new(Result)
 | 
						|
	simpleZero := spec.SimpleSchema{}
 | 
						|
	// Try to explain why... best guess
 | 
						|
	switch {
 | 
						|
	case pr.In == swaggerBody && (pr.SimpleSchema != simpleZero && pr.SimpleSchema.Type != objectType):
 | 
						|
		if isRef {
 | 
						|
			// Most likely, a $ref with a sibling is an unwanted situation: in itself this is a warning...
 | 
						|
			// but we detect it because of the following error:
 | 
						|
			// schema took over Parameter for an unexplained reason
 | 
						|
			res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
 | 
						|
		}
 | 
						|
		res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
 | 
						|
	case pr.In != swaggerBody && pr.Schema != nil:
 | 
						|
		if isRef {
 | 
						|
			res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
 | 
						|
		}
 | 
						|
		res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation))
 | 
						|
	case (pr.In == swaggerBody && pr.Schema == nil) || (pr.In != swaggerBody && pr.SimpleSchema == simpleZero):
 | 
						|
		// Other unexpected mishaps
 | 
						|
		res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
 | 
						|
	}
 | 
						|
	return res
 | 
						|
}
 | 
						|
 | 
						|
type responseHelper struct {
 | 
						|
	// A collection of unexported helpers for response resolution
 | 
						|
}
 | 
						|
 | 
						|
func (r *responseHelper) expandResponseRef(
 | 
						|
	response *spec.Response,
 | 
						|
	path string, s *SpecValidator) (*spec.Response, *Result) {
 | 
						|
	// Ensure response is expanded
 | 
						|
	var err error
 | 
						|
	res := new(Result)
 | 
						|
	if s.spec.SpecFilePath() == "" {
 | 
						|
		// there is no physical document to resolve $ref in response
 | 
						|
		err = spec.ExpandResponseWithRoot(response, s.spec.Spec(), nil)
 | 
						|
	} else {
 | 
						|
		err = spec.ExpandResponse(response, s.spec.SpecFilePath())
 | 
						|
	}
 | 
						|
	if err != nil { // Safeguard
 | 
						|
		// NOTE: we may enter here when the whole response is an unresolved $ref.
 | 
						|
		errorHelp.addPointerError(res, err, response.Ref.String(), path)
 | 
						|
		return nil, res
 | 
						|
	}
 | 
						|
 | 
						|
	return response, res
 | 
						|
}
 | 
						|
 | 
						|
func (r *responseHelper) responseMsgVariants(
 | 
						|
	responseType string,
 | 
						|
	responseCode int) (responseName, responseCodeAsStr string) {
 | 
						|
	// Path variants for messages
 | 
						|
	if responseType == jsonDefault {
 | 
						|
		responseCodeAsStr = jsonDefault
 | 
						|
		responseName = "default response"
 | 
						|
	} else {
 | 
						|
		responseCodeAsStr = strconv.Itoa(responseCode)
 | 
						|
		responseName = "response " + responseCodeAsStr
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 |