Summary
named.RenderBoolOption writes caller-supplied attributes before its canonical value=Bool.Name(). If params contain a value attribute, the output has duplicate attributes and browsers retain the first one, so the caller value overrides the Bool's name.
Class / severity
Correctness — Medium. A correctly constructed option can submit a value different from the backing named.Bool.Name, breaking select/input routing and server state.
Contract and valid usage
ui.Option documents that it is backed by a named.Bool, and Bool.Name is documented as the form value name. RequestWriter.NewUI accepts render params and Element.ApplyParams recognizes strings/template.HTMLAttr values as HTML attributes. No contract forbids a value attribute in params.
Reproduction
package ui_test
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/linkdata/jaws"
"github.com/linkdata/jaws/lib/htmlio"
"github.com/linkdata/jaws/lib/named"
"github.com/linkdata/jaws/lib/ui"
)
func TestOptionBoolNameWinsOverRenderParam(t *testing.T) {
jw, err := jaws.New()
if err != nil {
t.Fatal(err)
}
t.Cleanup(jw.Close)
rq := jw.NewRequest(httptest.NewRequest(http.MethodGet, "/", nil))
nb := named.NewBool(nil, "canonical", "label", false)
var out strings.Builder
rw := ui.RequestWriter{Request: rq, Writer: &out}
if err := rw.NewUI(ui.NewOption(nb), htmlio.Attr("value", "caller")); err != nil {
t.Fatal(err)
}
html := out.String()
canonical := strings.Index(html, `value="canonical"`)
caller := strings.Index(html, `value="caller"`)
if canonical < 0 || caller < 0 || canonical > caller {
t.Fatalf("canonical option value does not take precedence: %s", html)
}
}
Current output is equivalent to:
<option id="Jid.1" value="caller" value="canonical">label</option>
HTML parsing keeps the first duplicate attribute, so the live option value is caller.
Root cause
lib/named/namedbooloption.go:15-16 first collects params with elem.ApplyParams(params) and then appends the canonical value attribute. htmlio.WriteHTMLInner emits attributes in that order without de-duplicating them.
Impact
The browser sends the caller-supplied value instead of Bool.Name. Selection state can fail to map back to the named Bool or can select a different entry if names overlap.
Suggested fix
Make the canonical value take precedence, ideally by filtering conflicting value params. At minimum prepend the canonical value attribute before caller attrs, matching the ordering used by helpers with a dedicated value argument.
Confidence
Confirmed on main at 6fd13a2.
Summary
named.RenderBoolOptionwrites caller-supplied attributes before its canonicalvalue=Bool.Name(). If params contain avalueattribute, the output has duplicate attributes and browsers retain the first one, so the caller value overrides the Bool's name.Class / severity
Correctness — Medium. A correctly constructed option can submit a value different from the backing
named.Bool.Name, breaking select/input routing and server state.Contract and valid usage
ui.Optiondocuments that it is backed by anamed.Bool, andBool.Nameis documented as the form value name.RequestWriter.NewUIaccepts render params andElement.ApplyParamsrecognizes strings/template.HTMLAttrvalues as HTML attributes. No contract forbids avalueattribute in params.Reproduction
Current output is equivalent to:
HTML parsing keeps the first duplicate attribute, so the live option value is
caller.Root cause
lib/named/namedbooloption.go:15-16first collects params withelem.ApplyParams(params)and then appends the canonical value attribute.htmlio.WriteHTMLInneremits attributes in that order without de-duplicating them.Impact
The browser sends the caller-supplied value instead of
Bool.Name. Selection state can fail to map back to the named Bool or can select a different entry if names overlap.Suggested fix
Make the canonical value take precedence, ideally by filtering conflicting
valueparams. At minimum prepend the canonical value attribute before caller attrs, matching the ordering used by helpers with a dedicated value argument.Confidence
Confirmed on
mainat6fd13a2.