Skip to content

tinyxml2-go/config.go

:material-github: View on GitHub · 91 lines · live source, included at build time

// Package tinyxml2go provides a concurrent XML DOM parser with optional
// DoS-protection limits (input size, node count, nesting depth).
package tinyxml2go

import "errors"

// Common errors returned by configured parsing.
var (
    // ErrInputTooLarge is returned when input exceeds Config.MaxInputSize.
    ErrInputTooLarge = errors.New("input size exceeds maximum allowed")

    // ErrTooManyNodes is returned when the parsed node count exceeds Config.MaxNodeCount.
    ErrTooManyNodes = errors.New("node count exceeds maximum allowed")

    // ErrNestingTooDeep is returned when element nesting exceeds Config.MaxNestingDepth.
    ErrNestingTooDeep = errors.New("nesting depth exceeds maximum allowed")

    // ErrEmptyInput is returned when input is empty.
    ErrEmptyInput = errors.New("empty input")
)

// Config holds parsing limits used to guard against malicious or oversized
// input (DoS protection). The zero value of any individual limit means
// "unlimited" for that dimension; prefer DefaultConfig or StrictConfig for
// untrusted input.
type Config struct {
    // MaxInputSize limits the maximum XML input size in bytes.
    // Default: 100MB. Set to 0 for unlimited (not recommended).
    MaxInputSize int

    // MaxNodeCount limits the maximum number of element nodes parsed.
    // Default: 1,000,000. Set to 0 for unlimited (not recommended).
    MaxNodeCount int

    // MaxNestingDepth limits how deeply elements may nest. This prevents
    // stack exhaustion from deeply nested XML.
    // Default: 1,000. Set to 0 to disable this configurable limit — but note an
    // absolute hard ceiling of 10,000 levels always applies underneath, even to
    // this "unlimited" setting (see tinyxml2.go's maxNestingDepth).
    MaxNestingDepth int
}

// DefaultConfig returns the default configuration with sensible limits.
func DefaultConfig() *Config {
    return &Config{
        MaxInputSize:    100 * 1024 * 1024, // 100MB
        MaxNodeCount:    1_000_000,         // 1 million nodes
        MaxNestingDepth: 1_000,             // 1000 levels deep
    }
}

// StrictConfig returns a stricter configuration suitable for untrusted input.
func StrictConfig() *Config {
    return &Config{
        MaxInputSize:    10 * 1024 * 1024, // 10MB
        MaxNodeCount:    100_000,          // 100k nodes
        MaxNestingDepth: 100,              // 100 levels deep
    }
}

// UnlimitedConfig returns a configuration with no limits (use with caution).
func UnlimitedConfig() *Config {
    return &Config{
        MaxInputSize:    0, // Unlimited
        MaxNodeCount:    0, // Unlimited
        MaxNestingDepth: 0, // Unlimited
    }
}

// Validate checks that the configuration values are not negative.
func (c *Config) Validate() error {
    if c.MaxInputSize < 0 {
        return errors.New("MaxInputSize cannot be negative")
    }
    if c.MaxNodeCount < 0 {
        return errors.New("MaxNodeCount cannot be negative")
    }
    if c.MaxNestingDepth < 0 {
        return errors.New("MaxNestingDepth cannot be negative")
    }
    return nil
}

// validateInput checks that the input meets the size constraints.
func (c *Config) validateInput(data []byte) error {
    if len(data) == 0 {
        return ErrEmptyInput
    }
    if c.MaxInputSize > 0 && len(data) > c.MaxInputSize {
        return ErrInputTooLarge
    }
    return nil
}