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 } // decodeStrictOrdered is decodeStrict with every nested mapping kept as an // ordered yaml.MapSlice, so a file that is decoded, re-marshalled per entry and // decoded again keeps its key order at any depth and any indentation. func decodeStrictOrdered(raw []byte, dest any) error { dec := yaml.NewDecoder(bytes.NewReader(raw), yaml.DisallowUnknownField(), yaml.UseOrderedMap()) return dec.Decode(dest) } 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) } // assetPath resolves a YAML file reference to a path inside the plugin's // AdminFS: a plugin-relative path, ~/plugins///rest, or // WinterCMS's $///rest ($/ is the plugins directory). A $/ // path into another plugin cannot be read from this plugin's tree and is an // error. func assetPath(pluginID, ref string) (string, error) { ref = strings.TrimSpace(ref) own := strings.ReplaceAll(pluginID, ".", "/") + "/" if rest, ok := strings.CutPrefix(ref, "$/"); ok { if !strings.HasPrefix(rest, own) { return "", fmt.Errorf("$/ path %s names another plugin", ref) } ref = strings.TrimPrefix(rest, own) } ref = strings.TrimPrefix(ref, "~/") prefix := "plugins/" + own 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) }