Files
mstore/vendor/go.yaml.in/yaml/v4/plugin/limit/plugin.go
T
2026-06-16 08:02:19 +02:00

192 lines
4.8 KiB
Go

// Copyright 2026 The go-yaml Project Contributors
// SPDX-License-Identifier: Apache-2.0
// Package limit provides a configurable safety limit plugin for go-yaml.
//
// The limit plugin controls the maximum nesting depth and alias expansion
// ratio during YAML parsing.
// By default, go-yaml enforces conservative limits to prevent DoS attacks.
// This plugin lets you relax or tighten those limits for your use case.
//
// # Usage
//
// import (
// "go.yaml.in/yaml/v4"
// "go.yaml.in/yaml/v4/plugin/limit"
// )
//
// // Default limits
// loader := yaml.NewLoader(data, yaml.WithPlugin(limit.New()))
//
// // Disable alias checking (e.g. for 11,000 programmatic aliases)
// loader := yaml.NewLoader(data, yaml.WithPlugin(limit.New(limit.AliasNone())))
//
// // Custom depth limit
// loader := yaml.NewLoader(data, yaml.WithPlugin(limit.New(limit.DepthValue(50))))
//
// # Third-Party Plugins
//
// You can implement [yaml.LimitPlugin] directly instead of using this package:
//
// type StrictLimit struct{}
// func (s *StrictLimit) CheckDepth(depth int, ctx *yaml.DepthContext) error { ... }
// func (s *StrictLimit) CheckAlias(aliasCount, constructCount int) error { ... }
// yaml.NewLoader(data, yaml.WithPlugin(&StrictLimit{}))
package limit
import (
"fmt"
"go.yaml.in/yaml/v4/internal/libyaml"
)
// DepthContext is an alias for the type used in depth check callbacks.
// See [yaml.DepthContext] for field documentation.
type DepthContext = libyaml.DepthContext
// Plugin implements configurable safety limits for YAML parsing.
type Plugin struct {
depthLimit *int
depthDisabled bool
depthFn func(int, *DepthContext) error
aliasLimit *int
aliasDisabled bool
aliasFn func(int, int) error
}
// Option configures a [Plugin].
type Option func(*Plugin)
// New creates a limit plugin with the given options.
// With no options, it uses the same defaults as the bare go-yaml library.
func New(opts ...Option) *Plugin {
p := &Plugin{}
for _, o := range opts {
o(p)
}
return p
}
// DepthValue sets a maximum nesting depth (both flow and block).
func DepthValue(n int) Option {
return func(p *Plugin) {
p.depthLimit = &n
p.depthDisabled = false
p.depthFn = nil
}
}
// DepthNone disables depth checking entirely.
func DepthNone() Option {
return func(p *Plugin) {
p.depthDisabled = true
p.depthLimit = nil
p.depthFn = nil
}
}
// DepthFunc sets a custom depth check function.
func DepthFunc(fn func(depth int, ctx *DepthContext) error) Option {
return func(p *Plugin) {
p.depthFn = fn
p.depthLimit = nil
p.depthDisabled = false
}
}
// AliasValue sets a simple alias expansion count threshold.
func AliasValue(n int) Option {
return func(p *Plugin) {
p.aliasLimit = &n
p.aliasDisabled = false
p.aliasFn = nil
}
}
// AliasNone disables alias checking entirely.
func AliasNone() Option {
return func(p *Plugin) {
p.aliasDisabled = true
p.aliasLimit = nil
p.aliasFn = nil
}
}
// AliasFunc sets a custom alias check function.
func AliasFunc(fn func(aliasCount, constructCount int) error) Option {
return func(p *Plugin) {
p.aliasFn = fn
p.aliasLimit = nil
p.aliasDisabled = false
}
}
// CheckDepth implements [yaml.LimitPlugin].
func (p *Plugin) CheckDepth(depth int, ctx *DepthContext) error {
if p.depthFn != nil {
return p.depthFn(depth, ctx)
}
if p.depthDisabled {
return nil
}
if p.depthLimit != nil {
if depth > *p.depthLimit {
return fmt.Errorf("exceeded max depth of %d", *p.depthLimit)
}
return nil
}
return libyaml.DefaultDepthCheck(depth, ctx)
}
// CheckAlias implements [yaml.LimitPlugin].
func (p *Plugin) CheckAlias(aliasCount, constructCount int) error {
if p.aliasFn != nil {
return p.aliasFn(aliasCount, constructCount)
}
if p.aliasDisabled {
return nil
}
if p.aliasLimit != nil {
if aliasCount > *p.aliasLimit {
return fmt.Errorf("exceeded max alias count of %d", *p.aliasLimit)
}
return nil
}
return libyaml.DefaultAliasCheck(aliasCount, constructCount)
}
// NewFromYAML creates a limit plugin from a YAML config map.
// Keys: "depth" (int or null), "alias" (int or null).
// Null values disable the corresponding check.
// Omitted keys use defaults.
func NewFromYAML(cfg map[string]any) (*Plugin, error) {
var opts []Option
for key, val := range cfg {
switch key {
case "depth":
if val == nil {
opts = append(opts, DepthNone())
} else {
n, ok := val.(int)
if !ok {
return nil, fmt.Errorf("limit: depth must be int or null, got %T", val)
}
opts = append(opts, DepthValue(n))
}
case "alias":
if val == nil {
opts = append(opts, AliasNone())
} else {
n, ok := val.(int)
if !ok {
return nil, fmt.Errorf("limit: alias must be int or null, got %T", val)
}
opts = append(opts, AliasValue(n))
}
default:
return nil, fmt.Errorf("limit: unknown key %q", key)
}
}
return New(opts...), nil
}