feat(10.1-01): render header and form partials into an allowlisted node tree
- fields.yaml type: partial with a bare path name and config_list.yaml
headerPartial resolve to {ConfigDir}/_{name}.htm, parsed at boot; the
controller must implement pact.AdminPartialData
- html/template render against a curated view model, then x/net/html
ParseFragment and a tag, attribute and URL allowlist with 64 KiB, 2000-node
and depth-32 caps; the model type and trusted template types are refused
- GET .../partials/{name} with optional ?id= loaded through the form scope
- golang.org/x/net becomes a direct requirement (D-18), no new module
This commit is contained in:
@@ -23,7 +23,7 @@ var (
|
||||
formFieldTypes = map[string]struct{}{
|
||||
"text": {}, "textarea": {}, "number": {}, "checkbox": {},
|
||||
"switch": {}, "dropdown": {}, "relation": {}, "relation-manager": {},
|
||||
"widget": {},
|
||||
"widget": {}, "partial": {},
|
||||
}
|
||||
formSpans = map[string]struct{}{
|
||||
"left": {}, "right": {}, "full": {}, "auto": {}, "row": {},
|
||||
@@ -35,7 +35,7 @@ var (
|
||||
"label": {}, "comment": {}, "span": {}, "type": {}, "required": {},
|
||||
"tab": {}, "context": {}, "attributes": {}, "size": {}, "default": {},
|
||||
"nameFrom": {}, "emptyOption": {}, "options": {}, "relation": {},
|
||||
"widget": {}, "action": {}, "fill": {},
|
||||
"widget": {}, "action": {}, "fill": {}, "path": {},
|
||||
}
|
||||
// widgetKeys are valid only on `type: widget` (D-06).
|
||||
widgetKeys = []string{"widget", "action", "fill"}
|
||||
@@ -348,9 +348,6 @@ func compileFieldNode(name string, node ast.Node) (FormField, error) {
|
||||
if err != nil || typ == "" {
|
||||
return FormField{}, fmt.Errorf("type is required")
|
||||
}
|
||||
if typ == "partial" {
|
||||
return FormField{}, fmt.Errorf("type partial is not supported")
|
||||
}
|
||||
if _, ok := formFieldTypes[typ]; !ok {
|
||||
return FormField{}, fmt.Errorf("unsupported type %s", typ)
|
||||
}
|
||||
@@ -422,6 +419,9 @@ func compileFieldNode(name string, node ast.Node) (FormField, error) {
|
||||
if err := compileWidgetKeys(typ, values, &field); err != nil {
|
||||
return FormField{}, err
|
||||
}
|
||||
if err := compilePartialPath(typ, values, &field); err != nil {
|
||||
return FormField{}, err
|
||||
}
|
||||
if node, ok := values["required"]; ok {
|
||||
field.Required, err = nodeBool(node)
|
||||
if err != nil {
|
||||
@@ -498,6 +498,32 @@ func compileWidgetKeys(typ string, values map[string]ast.Node, field *FormField)
|
||||
return nil
|
||||
}
|
||||
|
||||
// partialPathHint is the D-11 path rule shared by every partial path error.
|
||||
const partialPathHint = "path must be a partial name such as summary (resolves to CONFIG_DIR/_summary.htm); Winter $/ and ~/ paths are not supported"
|
||||
|
||||
// compilePartialPath decodes the path of a `type: partial` field (D-09). It
|
||||
// is refused on any other type and must be a bare partial name, so no
|
||||
// free-form path ever reaches the plugin's file tree. The template itself is
|
||||
// read and parsed in compileExtension.
|
||||
func compilePartialPath(typ string, values map[string]ast.Node, field *FormField) error {
|
||||
node, ok := values["path"]
|
||||
if typ != "partial" {
|
||||
if ok {
|
||||
return fmt.Errorf("path is only valid on type: partial")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("type partial needs a path: %s", partialPathHint)
|
||||
}
|
||||
name, err := nodeString(node)
|
||||
if err != nil || !identifier(name) {
|
||||
return fmt.Errorf("partial %q: %s", nodeText(node), partialPathHint)
|
||||
}
|
||||
field.Path = name
|
||||
return nil
|
||||
}
|
||||
|
||||
func compileContext(node ast.Node) (*fieldContext, error) {
|
||||
switch n := node.(type) {
|
||||
case *ast.StringNode:
|
||||
|
||||
Reference in New Issue
Block a user