feat(19.4-03): tide captures a named input of an HTML response

- capture rule from: response.form reads the value of the <input> with
  the rule's name (a server-rendered form's one-shot token), so a parity
  flow can post a consent form back
- README and parity docs list the capture sources
This commit is contained in:
2026-10-19 19:59:24 +02:00
parent 1e37926ee9
commit bb4b8d448d
4 changed files with 79 additions and 3 deletions

View File

@@ -14,6 +14,7 @@ import (
"sync"
"github.com/goccy/go-yaml"
"golang.org/x/net/html"
)
var (
@@ -322,6 +323,12 @@ func extractCapture(rule CaptureRule, req Request, resp Response) (string, error
return "", fmt.Errorf("missing Location query %s", rule.Name)
}
return v, nil
case "response.form":
v := htmlInputValue(string(resp.Body), rule.Name)
if v == "" {
return "", fmt.Errorf("missing response form field %s", rule.Name)
}
return v, nil
case "request.form":
v, err := formValue(string(req.Body), rule.Name)
if err != nil {
@@ -352,6 +359,36 @@ func extractCapture(rule CaptureRule, req Request, resp Response) (string, error
}
}
// htmlInputValue returns the value attribute of the first <input> element
// named name in an HTML body (a server-rendered form's hidden field, such
// as a one-shot CSRF or consent token), or "".
func htmlInputValue(body, name string) string {
z := html.NewTokenizer(strings.NewReader(body))
for {
switch z.Next() {
case html.ErrorToken:
return ""
case html.StartTagToken, html.SelfClosingTagToken:
tok := z.Token()
if tok.Data != "input" {
continue
}
var n, v string
for _, a := range tok.Attr {
switch a.Key {
case "name":
n = a.Val
case "value":
v = a.Val
}
}
if n == name {
return v
}
}
}
}
func formValue(body, name string) (string, error) {
vals, err := url.ParseQuery(body)
if err != nil {