diff --git a/modules/brother/README.md b/modules/brother/README.md index 096142dc..98328181 100644 --- a/modules/brother/README.md +++ b/modules/brother/README.md @@ -62,7 +62,7 @@ makes it (mode 0755) when it files the first scan. | `printer.status` | without a login: over IPP (`Get-Printer-Attributes`, encoded by hand, on port 631) the state (idle, processing, stopped), the state reasons (such as `toner-low-warning`) and the markers' names, levels (percent; below 0 is unknown) and colours; over eSCL the scanner's state and its feeder's. A part that does not answer is `answers: false` with why. Needs `printer` and `scanner` | | `printer.upload` | one part of a document to print later (`upload` id, `part`, `parts`, `sha256` of the whole, `data` base64): stored as `uploads/.pdf` or `.jpg` once all parts came and the sha256 matches; a wrong sha256, a part out of range or a document that is neither PDF nor JPEG stores nothing | | `printer.print` | prints a kept scan (`object`) or a completed upload (`upload`) as an IPP Print-Job (operation 0x0002, port 631) in its own format, `copies` 1 to 99 (default 1), `sides` one-sided, two-sided-long-edge or two-sided-short-edge (default one-sided), `media` (default `iso_a4_210x297mm`); answers the job's id and state | -| `printer.settings` | asks the device process (below) to log in, read one admin page and log out, and waits up to 60 s for its answer. Argument `page`: a path on the admin site, starting with `/` and ending in `.html` (default `/general/status.html`). Answers the page's title, links (href and text) and forms with their fields (name, type, value, label); a password field's value is never given. Read-only: it sends the login and the logout and nothing else | +| `printer.settings` | asks the device process (below) to log in, read one admin page and log out, and waits up to 60 s for its answer. Argument `page`: a path on the admin site, starting with `/` and ending in `.html` (default `/general/status.html`). Answers the page's title and forms with their fields, each with its current value (a radio group as one field with the checked button's value, a checkbox with `checked`, a select with the selected option) and `options`; the page's links only with `links: true`, hidden fields only with `hidden: true`; a password field's value is never given. Read-only: it sends the login and the logout and nothing else | ### Trust and the password diff --git a/modules/brother/cmd/brother/deviceproc.go b/modules/brother/cmd/brother/deviceproc.go index c615ca30..24a5da9b 100644 --- a/modules/brother/cmd/brother/deviceproc.go +++ b/modules/brother/cmd/brother/deviceproc.go @@ -120,7 +120,7 @@ func (w *deviceWorker) Handle(body []byte, early func()) (action, time.Duration) func (w *deviceWorker) run(ctx context.Context, ask Ask) (map[string]any, error) { switch ask.Op { case OpSettings: - return w.read(ctx, ask.Page) + return w.read(ctx, ask.Page, ask.Links, ask.Hidden) case OpScan: return w.scan(ctx) case OpDocument: @@ -301,7 +301,7 @@ func (w *deviceWorker) print(ctx context.Context, ask Ask) (map[string]any, erro // read logs in, reads one page of the admin site and logs out. It is read-only: the login (and the logout) are // the only things it sends, and a password field's value is never in the answer. The printer is not contacted // before the settings and the password are known to be given (1453/AC4). -func (w *deviceWorker) read(ctx context.Context, page string) (map[string]any, error) { +func (w *deviceWorker) read(ctx context.Context, page string, links, hidden bool) (map[string]any, error) { if page == "" { page = DefaultPage } @@ -342,13 +342,39 @@ func (w *deviceWorker) read(ctx context.Context, page string) (map[string]any, e } logout := a.Logout(lctx, body, at) p := ParsePage(body) - return map[string]any{ + out := map[string]any{ "page": page, "title": p.Title, - "links": nonNil(p.Links), - "forms": nonNilForms(p.Forms), + "forms": nonNilForms(withoutHidden(p.Forms, hidden)), "logout": logout, - }, nil + } + if links { + out["links"] = nonNil(p.Links) + } + return out, nil +} + +// withoutHidden leaves the hidden fields (the CSRF token and the like, noise to a reader) out of the forms +// unless they are asked for. +func withoutHidden(forms []Form, keep bool) []Form { + if keep { + return forms + } + out := make([]Form, 0, len(forms)) + for _, f := range forms { + var fields []Field + for _, fl := range f.Fields { + if fl.Type != "hidden" { + fields = append(fields, fl) + } + } + if len(fields) == 0 && len(f.Fields) > 0 { + continue // a form of nothing but hidden fields (the logout form) has nothing to read + } + f.Fields = fields + out = append(out, f) + } + return out } func nonNil(l []Link) []Link { diff --git a/modules/brother/cmd/brother/html.go b/modules/brother/cmd/brother/html.go index a3f5e136..60b598c6 100644 --- a/modules/brother/cmd/brother/html.go +++ b/modules/brother/cmd/brother/html.go @@ -21,9 +21,19 @@ type Field struct { Name string `json:"name"` Type string `json:"type"` Value string `json:"value"` + Checked *bool `json:"checked,omitempty"` Label string `json:"label"` - Options []string `json:"options,omitempty"` + Options []Option `json:"options,omitempty"` id string + own string // the label of this control alone: