fix(form): rename blank add_field name to _unnamed_<n> instead of emitting empty name#60
Conversation
📝 WalkthroughWalkthroughThe PR fixes empty or whitespace-only field names in the non-strict ChangesEmpty field name placeholder handling
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/multipartkit/form.gleam (1)
181-182:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate
add_field_strictdocumentation to reflect the newadd_fieldbehavior.The docstring states that
add_field("name\n", _)produces a part withname="", but after this PR's changes, it now produces a part withname="_unnamed_0"(or the appropriate position-based placeholder). This documentation should be updated to accurately describe the current behavior of the non-strict variant, so users understand the difference between strict and non-strict paths.For example:
/// The non-strict `add_field` silently strips control bytes and renames /// empty or stripped-to-empty names to a generated placeholder (so /// `add_field("name\n", _)` produces a part with `name="_unnamed_0"`).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/multipartkit/form.gleam` around lines 181 - 182, Update the add_field_strict docstring to accurately describe the new behavior of the non-strict variant: explain that add_field (non-strict) strips control bytes and, when a name becomes empty after stripping, replaces it with a generated placeholder (e.g. "_unnamed_0" or position-based placeholder) rather than producing an empty name; reference add_field and add_field_strict so maintainers can locate the docs to change the example text accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/multipartkit/form.gleam`:
- Around line 181-182: Update the add_field_strict docstring to accurately
describe the new behavior of the non-strict variant: explain that add_field
(non-strict) strips control bytes and, when a name becomes empty after
stripping, replaces it with a generated placeholder (e.g. "_unnamed_0" or
position-based placeholder) rather than producing an empty name; reference
add_field and add_field_strict so maintainers can locate the docs to change the
example text accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 358a9b46-1b48-4f3c-88e6-69f7a677c7e3
📒 Files selected for processing (3)
CHANGELOG.mdsrc/multipartkit/form.gleamtest/regression_add_field_empty_name_test.gleam
Summary
The lenient
multipartkit/form.add_fieldsilently produced a part withContent-Disposition: form-data; name=""when given an emptyname, or anamethat became empty after CR/LF/NUL stripping. RFC 7578 §4.2 requires thenameparameter to be the field name, and an empty name produces a wire image whose receiver interpretation is implementation-defined (skip the field, overwrite siblings keyed on"", or reject the whole body). Onlyadd_field_strictrejected this; the default path was a silent data-loss / non-compliant-wire vector. This closes the two regression reports #57 (CR/LF stripped to empty) and #58 (empty name accepted), which share the same root cause.Changes
This implements the issues' "option 2: normalise loudly". The lenient
add_fieldnow renames a blank name (empty, or empty/whitespace-only after stripping) to a generated"_unnamed_<n>"placeholder, where<n>is the part's zero-based position in the form. The observablenameis therefore neverSome(""), the part stays RFC 7578-addressable, and theadd_fieldsignature stays-> Form(no breaking API change). A name that is merely padded (e.g." a ") or only partially stripped (e.g."ab\ncd"->"abcd") is kept as-is — only an entirely blank name is rewritten.add_field_strictis unchanged: callers that want bad names surfaced asError(EmptyFieldName(_))/Error(NameContainsControlBytes(_))rather than renamed should keep using it.Added
test/regression_add_field_empty_name_test.gleamcovering the empty / CR-LF-only / NUL-only / whitespace-only cases, the partial-strip and padded cases that must be kept verbatim, the normal-name baseline, and placeholder index tracking across interleaved blank and non-blank fields.Design Decisions
Option 2 was chosen over option 1 (reject in
add_field) and option 3 (reject atencode_form) because it is the only option that both satisfies the issues' Definition-of-Done (name never round-trips toSome("")) and keeps the existing backward-compatibility pinadd_field_non_strict_empty_name_still_accepted_testgreen — that test asserts the non-strict path still produces exactly one part, so dropping the part (option 1) or returning aResultfromencode_form(option 3) would have been a larger, breaking change. Renaming preserves the caller's value under a visible, detectable synthetic name rather than discarding it.The whitespace-only case is normalised too, matching the "empty" notion that
add_field_strictalready rejects viastring.trim.Limitations
The
_unnamed_<n>placeholder is position-based, not collision-proof: a caller who also supplies a literal"_unnamed_<k>"as a real field name can end up with two parts sharing that name. This is documented onadd_fieldand is strictly better than the previousname=""behaviour.add_file'snamechannel (and thefilenamechannel) are intentionally left unchanged — both issues explicitly deferadd_fileto a separate ticket.Closes #57
Closes #58
Summary by CodeRabbit
add_fieldto prevent empty field names from being emitted. Names that become empty after sanitization are now automatically replaced with generated placeholders (_unnamed_<n>) based on field position, ensuring RFC 7578 compliance.