diff --git a/content/reference/client-attributes.md b/content/reference/client-attributes.md index afcab42..d7f1a7f 100644 --- a/content/reference/client-attributes.md +++ b/content/reference/client-attributes.md @@ -1051,6 +1051,7 @@ Directives use CSS custom properties for configuration: `--lvt-scroll-behavior`, | Attribute | Description | Example | |-----------|-------------|---------| | `lvt-upload` | File upload identifier | `lvt-upload="avatar"` | +| `lvt-upload-with` | Send this field along with an upload fired from the same form. Opt-in — unmarked fields never reach the upload endpoint | `` | ### Preservation Attributes diff --git a/content/reference/uploads.md b/content/reference/uploads.md index eb0f02d..38b4d76 100644 --- a/content/reference/uploads.md +++ b/content/reference/uploads.md @@ -67,11 +67,44 @@ func (c *Controller) OnUpload(part *livetemplate.UploadPart, ctx *livetemplate.C ``` Inside `OnUpload`, `ctx` carries the request identity (`ctx.UserID()`, -`ctx.GroupID()`) **and** the form fields parsed *before* this file part, read via -`ctx.GetString(...)`. Because parts stream in body order, a field is visible only -if its input precedes the file input — so order any field you need mid-stream -(e.g. the record id to route the bytes to the right destination) ahead of the -file input. Fields after the file part reach only the follow-on action. +`ctx.GroupID()`) **and** the form fields the upload was told to carry, read via +`ctx.GetString(...)`. + +#### Sending form fields with an upload + +A Proxied upload auto-fires the moment a file is selected — there is no submit +for the user to review — so nothing from the enclosing form travels unless you +mark it with `lvt-upload-with`: + +```html +
+``` + +Here `id` reaches `OnUpload` and `csrf` does not. Mark only what the handler +needs: unmarked fields — session tokens, co-located credentials, anything else +that happens to share the form — never reach the upload endpoint. Forgetting to +mark a field you *do* need surfaces as a missing value in `OnUpload`, which is +why the default is off. + +Three rules apply on top of the marking: + +- **Order matters.** Parts stream in body order, so a marked field is readable + mid-stream only if its input precedes the file input. Fields after the file + part reach only the follow-on action. +- **Marking is by name, and form-scoped.** A marked field travels with every + upload fired from its form, and marking any one member of a radio or checkbox + group opts in the whole group. Which values actually send is still the + browser's usual successful-control behaviour — an unchecked box sends nothing. +- **The file wins a name collision.** If a marked field shares the upload + field's name, the file part replaces it. + +When one selection carries several files, each is POSTed as its own request and +the marked fields ride along with every one, so each reaches `OnUpload` +self-describing. The reader enforces `MaxFileSize` mid-stream, returning `ErrUploadTooLarge` (a distinct sentinel, not `io.EOF`) so a truncated stream aborts instead of diff --git a/examples/upload-modes/upload-modes.tmpl b/examples/upload-modes/upload-modes.tmpl index 85ea4a6..b7a4b2b 100644 --- a/examples/upload-modes/upload-modes.tmpl +++ b/examples/upload-modes/upload-modes.tmpl @@ -30,10 +30,11 @@Bytes stream through the server to storage with zero local-disk staging. The
- record id below rides along in the same multipart POST, ahead of the file
- part, so OnUpload reads it mid-stream and routes the bytes per record.
lvt-upload-with, so it rides along in the
+ same multipart POST, ahead of the file part — OnUpload reads it
+ mid-stream and routes the bytes per record. Unmarked fields stay put.
{{if .ProxiedRef}}