package cabana import ( "bytes" "fmt" "io/fs" "path" "strings" "github.com/goccy/go-yaml" ) func decodeStrict(raw []byte, dest any) error { dec := yaml.NewDecoder(bytes.NewReader(raw), yaml.DisallowUnknownField()) if err := dec.Decode(dest); err != nil { return err } return nil } func readAsset(fsys fs.FS, name string) ([]byte, error) { name = path.Clean(name) if name == "." || strings.HasPrefix(name, "..") || strings.Contains(name, "..") { return nil, fmt.Errorf("path escapes the plugin") } return fs.ReadFile(fsys, name) } func assetPath(pluginID, ref string) (string, error) { ref = strings.TrimSpace(ref) ref = strings.TrimPrefix(ref, "~/") prefix := "plugins/" + strings.ReplaceAll(pluginID, ".", "/") + "/" ref = strings.TrimPrefix(ref, prefix) ref = path.Clean(ref) if ref == "." || strings.HasPrefix(ref, "..") || strings.Contains(ref, "..") { return "", fmt.Errorf("list path escapes the plugin") } return ref, nil } func identifier(s string) bool { if s == "" { return false } for i, r := range s { switch { case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r == '_': case i > 0 && r >= '0' && r <= '9': default: return false } } return true } func bootErr(pluginID, controllerID, file string, err error) error { return fmt.Errorf("cabana: admin schema %s/%s/%s: %w", pluginID, controllerID, file, err) }