Skip to content

named: caller value attributes override Bool.Name in rendered options #218

Description

@linkdata

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions