mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
* Upgrade gqlgen to v0.17.2 This enables builds on Go 1.18. github.com/vektah/gqlparser is upgraded to the newest version too. Getting this to work is a bit of a hazzle. I had to first remove vendoring from the repository, perform the upgrade and then re-introduce the vendor directory. I think gqlgens analysis went wrong for some reason on the upgrade. It would seem a clean-room installation fixed it. * Bump project to 1.18 * Update all packages, address gqlgenc breaking changes * Let `go mod tidy` handle the go.mod file * Upgrade linter to 1.45.2 * Introduce v1.45.2 of the linter The linter now correctly warns on `strings.Title` because it isn't unicode-aware. Fix this by using the suggested fix from x/text/cases to produce unicode-aware strings. The mapping isn't entirely 1-1 as this new approach has a larger iface: it spans all of unicode rather than just ASCII. It coincides for ASCII however, so things should be largely the same. * Ready ourselves for errchkjson and contextcheck. * Revert dockerfile golang version changes for now Co-authored-by: Kermie <kermie@isinthe.house> Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
110 lines
3.2 KiB
Go
110 lines
3.2 KiB
Go
package complexity
|
|
|
|
import (
|
|
"github.com/99designs/gqlgen/graphql"
|
|
"github.com/vektah/gqlparser/v2/ast"
|
|
)
|
|
|
|
func Calculate(es graphql.ExecutableSchema, op *ast.OperationDefinition, vars map[string]interface{}) int {
|
|
walker := complexityWalker{
|
|
es: es,
|
|
schema: es.Schema(),
|
|
vars: vars,
|
|
}
|
|
return walker.selectionSetComplexity(op.SelectionSet)
|
|
}
|
|
|
|
type complexityWalker struct {
|
|
es graphql.ExecutableSchema
|
|
schema *ast.Schema
|
|
vars map[string]interface{}
|
|
}
|
|
|
|
func (cw complexityWalker) selectionSetComplexity(selectionSet ast.SelectionSet) int {
|
|
var complexity int
|
|
for _, selection := range selectionSet {
|
|
switch s := selection.(type) {
|
|
case *ast.Field:
|
|
fieldDefinition := cw.schema.Types[s.Definition.Type.Name()]
|
|
|
|
if fieldDefinition.Name == "__Schema" {
|
|
continue
|
|
}
|
|
|
|
var childComplexity int
|
|
switch fieldDefinition.Kind {
|
|
case ast.Object, ast.Interface, ast.Union:
|
|
childComplexity = cw.selectionSetComplexity(s.SelectionSet)
|
|
}
|
|
|
|
args := s.ArgumentMap(cw.vars)
|
|
var fieldComplexity int
|
|
if s.ObjectDefinition.Kind == ast.Interface {
|
|
fieldComplexity = cw.interfaceFieldComplexity(s.ObjectDefinition, s.Name, childComplexity, args)
|
|
} else {
|
|
fieldComplexity = cw.fieldComplexity(s.ObjectDefinition.Name, s.Name, childComplexity, args)
|
|
}
|
|
complexity = safeAdd(complexity, fieldComplexity)
|
|
|
|
case *ast.FragmentSpread:
|
|
complexity = safeAdd(complexity, cw.selectionSetComplexity(s.Definition.SelectionSet))
|
|
|
|
case *ast.InlineFragment:
|
|
complexity = safeAdd(complexity, cw.selectionSetComplexity(s.SelectionSet))
|
|
}
|
|
}
|
|
return complexity
|
|
}
|
|
|
|
func (cw complexityWalker) interfaceFieldComplexity(def *ast.Definition, field string, childComplexity int, args map[string]interface{}) int {
|
|
// Interfaces don't have their own separate field costs, so they have to assume the worst case.
|
|
// We iterate over all implementors and choose the most expensive one.
|
|
maxComplexity := 0
|
|
implementors := cw.schema.GetPossibleTypes(def)
|
|
for _, t := range implementors {
|
|
fieldComplexity := cw.fieldComplexity(t.Name, field, childComplexity, args)
|
|
if fieldComplexity > maxComplexity {
|
|
maxComplexity = fieldComplexity
|
|
}
|
|
}
|
|
return maxComplexity
|
|
}
|
|
|
|
func (cw complexityWalker) fieldComplexity(object, field string, childComplexity int, args map[string]interface{}) int {
|
|
if customComplexity, ok := cw.es.Complexity(object, field, childComplexity, args); ok && customComplexity >= childComplexity {
|
|
return customComplexity
|
|
}
|
|
// default complexity calculation
|
|
return safeAdd(1, childComplexity)
|
|
}
|
|
|
|
const maxInt = int(^uint(0) >> 1)
|
|
|
|
// safeAdd is a saturating add of a and b that ignores negative operands.
|
|
// If a + b would overflow through normal Go addition,
|
|
// it returns the maximum integer value instead.
|
|
//
|
|
// Adding complexities with this function prevents attackers from intentionally
|
|
// overflowing the complexity calculation to allow overly-complex queries.
|
|
//
|
|
// It also helps mitigate the impact of custom complexities that accidentally
|
|
// return negative values.
|
|
func safeAdd(a, b int) int {
|
|
// Ignore negative operands.
|
|
if a < 0 {
|
|
if b < 0 {
|
|
return 1
|
|
}
|
|
return b
|
|
} else if b < 0 {
|
|
return a
|
|
}
|
|
|
|
c := a + b
|
|
if c < a {
|
|
// Set c to maximum integer instead of overflowing.
|
|
c = maxInt
|
|
}
|
|
return c
|
|
}
|