diff --git a/api/router.go b/api/router.go index a020c57..99754d2 100644 --- a/api/router.go +++ b/api/router.go @@ -147,7 +147,7 @@ func (mh *mainHandler) handle(w http.ResponseWriter, r *http.Request) error { "default-src 'self'; "+ "connect-src https://*.safing.io 'self'; "+ "style-src 'self' 'unsafe-inline'; "+ - "img-src 'self' data:", + "img-src 'self' data: blob:", ) } diff --git a/config/option.go b/config/option.go index a1f4aa4..ac11035 100644 --- a/config/option.go +++ b/config/option.go @@ -66,6 +66,9 @@ type PossibleValue struct { // Format: :: //. type Annotations map[string]interface{} +// MigrationFunc is a function that migrates a config option value. +type MigrationFunc func(option *Option, value any) any + // Well known annotations defined by this package. const ( // DisplayHintAnnotation provides a hint for the user @@ -259,6 +262,9 @@ type Option struct { // Annotations is considered mutable and setting/reading annotation keys // must be performed while the option is locked. Annotations Annotations + // Migrations holds migration functions that are given the raw option value + // before any validation is run. The returned value is then used. + Migrations []MigrationFunc `json:"-"` activeValue *valueCache // runtime value (loaded from config file or set by user) activeDefaultValue *valueCache // runtime default value (may be set internally) @@ -361,6 +367,7 @@ func (option *Option) ValidateValue(value any) error { option.Lock() defer option.Unlock() + value = migrateValue(option, value) if _, err := validateValue(option, value); err != nil { return err } diff --git a/config/perspective.go b/config/perspective.go index 9351051..aaebcf7 100644 --- a/config/perspective.go +++ b/config/perspective.go @@ -35,6 +35,8 @@ optionsLoop: if !ok { continue } + // migrate value + configValue = migrateValue(option, configValue) // validate value valueCache, err := validateValue(option, configValue) if err != nil { diff --git a/config/set.go b/config/set.go index 378e819..daeef6a 100644 --- a/config/set.go +++ b/config/set.go @@ -56,6 +56,7 @@ func ValidateConfig(newValues map[string]interface{}) (validationErrors []*Valid option.Lock() defer option.Unlock() + newValue = migrateValue(option, newValue) _, err := validateValue(option, newValue) if err != nil { validationErrors = append(validationErrors, err) @@ -88,6 +89,7 @@ func ReplaceConfig(newValues map[string]interface{}) (validationErrors []*Valida option.activeValue = nil if ok { + newValue = migrateValue(option, newValue) valueCache, err := validateValue(option, newValue) if err == nil { option.activeValue = valueCache @@ -125,6 +127,7 @@ func ReplaceDefaultConfig(newValues map[string]interface{}) (validationErrors [] option.activeDefaultValue = nil if ok { + newValue = migrateValue(option, newValue) valueCache, err := validateValue(option, newValue) if err == nil { option.activeDefaultValue = valueCache @@ -160,6 +163,7 @@ func setConfigOption(key string, value any, push bool) (err error) { if value == nil { option.activeValue = nil } else { + value = migrateValue(option, value) valueCache, vErr := validateValue(option, value) if vErr == nil { option.activeValue = valueCache @@ -201,6 +205,7 @@ func setDefaultConfigOption(key string, value interface{}, push bool) (err error if value == nil { option.activeDefaultValue = nil } else { + value = migrateValue(option, value) valueCache, vErr := validateValue(option, value) if vErr == nil { option.activeDefaultValue = valueCache diff --git a/config/validate.go b/config/validate.go index 4120d2e..c5f3cc3 100644 --- a/config/validate.go +++ b/config/validate.go @@ -5,6 +5,8 @@ import ( "fmt" "math" "reflect" + + "github.com/safing/portbase/log" ) type valueCache struct { @@ -64,6 +66,18 @@ func isAllowedPossibleValue(opt *Option, value interface{}) error { return errors.New("value is not allowed") } +// migrateValue runs all value migrations. +func migrateValue(option *Option, value any) any { + for _, migration := range option.Migrations { + newValue := migration(option, value) + if newValue != value { + log.Debugf("config: migrated %s value from %v to %v", option.Key, value, newValue) + } + value = newValue + } + return value +} + // validateValue ensures that value matches the expected type of option. // It does not create a copy of the value! func validateValue(option *Option, value interface{}) (*valueCache, *ValidationError) { //nolint:gocyclo @@ -76,8 +90,6 @@ func validateValue(option *Option, value interface{}) (*valueCache, *ValidationE } } - reflect.TypeOf(value).ConvertibleTo(reflect.TypeOf("")) - var validated *valueCache switch v := value.(type) { case string: