e5.0.0 Tests added, bug fixes.

This commit is contained in:
2025-07-19 19:05:17 -04:00
parent 5a58db6108
commit e9b55063ff
19 changed files with 2143 additions and 339 deletions

View File

@ -57,7 +57,7 @@ func (c *Config) unmarshal(basePath string, source Source, target any) error {
// Create decoder with comprehensive hooks
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: target,
TagName: "toml",
TagName: c.tagName,
WeaklyTypedInput: true,
DecodeHook: c.getDecodeHook(),
ZeroFields: true,
@ -77,16 +77,16 @@ func (c *Config) unmarshal(basePath string, source Source, target any) error {
// getDecodeHook returns the composite decode hook for all type conversions
func (c *Config) getDecodeHook() mapstructure.DecodeHookFunc {
return mapstructure.ComposeDecodeHookFunc(
// Standard hooks
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
// Network types
stringToNetIPHookFunc(),
stringToNetIPNetHookFunc(),
stringToURLHookFunc(),
// Standard hooks
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToTimeHookFunc(time.RFC3339),
mapstructure.StringToSliceHookFunc(","),
// Custom application hooks
c.customDecodeHook(),
)
@ -94,7 +94,7 @@ func (c *Config) getDecodeHook() mapstructure.DecodeHookFunc {
// stringToNetIPHookFunc handles net.IP conversion
func stringToNetIPHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
@ -120,27 +120,28 @@ func stringToNetIPHookFunc() mapstructure.DecodeHookFunc {
// stringToNetIPNetHookFunc handles net.IPNet conversion
func stringToNetIPNetHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IPNet{}) && t != reflect.TypeOf(&net.IPNet{}) {
isPtr := t.Kind() == reflect.Ptr
targetType := t
if isPtr {
targetType = t.Elem()
}
if targetType != reflect.TypeOf(net.IPNet{}) {
return data, nil
}
str := data.(string)
// SECURITY: Validate CIDR format
if len(str) > 49 { // Max IPv6 CIDR length
return nil, fmt.Errorf("invalid CIDR length: %d", len(str))
}
_, ipnet, err := net.ParseCIDR(str)
if err != nil {
return nil, fmt.Errorf("invalid CIDR: %w", err)
}
if t == reflect.TypeOf(&net.IPNet{}) {
if isPtr {
return ipnet, nil
}
return *ipnet, nil
@ -149,27 +150,28 @@ func stringToNetIPNetHookFunc() mapstructure.DecodeHookFunc {
// stringToURLHookFunc handles url.URL conversion
func stringToURLHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(url.URL{}) && t != reflect.TypeOf(&url.URL{}) {
isPtr := t.Kind() == reflect.Ptr
targetType := t
if isPtr {
targetType = t.Elem()
}
if targetType != reflect.TypeOf(url.URL{}) {
return data, nil
}
str := data.(string)
// SECURITY: Validate URL length to prevent DoS
if len(str) > 2048 {
return nil, fmt.Errorf("URL too long: %d bytes", len(str))
}
u, err := url.Parse(str)
if err != nil {
return nil, fmt.Errorf("invalid URL: %w", err)
}
if t == reflect.TypeOf(&url.URL{}) {
if isPtr {
return u, nil
}
return *u, nil
@ -178,7 +180,7 @@ func stringToURLHookFunc() mapstructure.DecodeHookFunc {
// customDecodeHook allows for application-specific type conversions
func (c *Config) customDecodeHook() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
// SECURITY: Add custom validation for application types here
// Example: Rate limit parsing, permission validation, etc.
@ -215,4 +217,4 @@ func navigateToPath(nested map[string]any, path string) any {
}
return current
}
}