Summary
internal/apiform detects io.Reader implementations before it applies the encoder's existing nil-pointer handling. A typed nil pointer whose type implements io.Reader can therefore be routed into io.Copy, which invokes Read on a nil receiver and can panic.
The encoder already defines nil pointers as empty form fields, so reader detection currently bypasses an intended invariant.
Reproduction
On current main (d082a010f7c6cacf407d8a1581446a7857f9f1bb), a minimal reader type demonstrates the problem:
type panicReader struct{}
func (*panicReader) Read([]byte) (int, error) {
panic("Read called on nil receiver")
}
var reader *panicReader
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
_ = Marshal(map[string]any{"file": reader}, writer)
Because *panicReader implements io.Reader, encodeValue checks:
if t.Implements(reflect.TypeOf((*io.Reader)(nil)).Elem()) {
return e.encodeReader(key, val, writer)
}
before reaching the pointer branch:
case reflect.Pointer:
if val.IsNil() || !val.IsValid() {
return writer.WriteField(key, "")
}
The nil pointer is therefore converted to an io.Reader interface and passed to io.Copy, which calls its Read method.
Expected behavior
A typed nil pointer should follow the same nil-pointer semantics regardless of whether its type happens to implement io.Reader: encode an empty field and do not invoke methods on the nil receiver.
Suggested fix
Check nil pointer/interface values before reader-interface detection. Keep reader handling unchanged for non-nil values.
Add a regression using a typed nil reader whose Read method panics if invoked, and verify multipart encoding completes without a panic and emits an empty field.
Impact
This is robustness and request-serialization correctness. A legitimate typed nil value can currently turn request construction into a process panic instead of being handled by the encoder's existing null-value path.
Summary
internal/apiformdetectsio.Readerimplementations before it applies the encoder's existing nil-pointer handling. A typed nil pointer whose type implementsio.Readercan therefore be routed intoio.Copy, which invokesReadon a nil receiver and can panic.The encoder already defines nil pointers as empty form fields, so reader detection currently bypasses an intended invariant.
Reproduction
On current
main(d082a010f7c6cacf407d8a1581446a7857f9f1bb), a minimal reader type demonstrates the problem:Because
*panicReaderimplementsio.Reader,encodeValuechecks:before reaching the pointer branch:
The nil pointer is therefore converted to an
io.Readerinterface and passed toio.Copy, which calls itsReadmethod.Expected behavior
A typed nil pointer should follow the same nil-pointer semantics regardless of whether its type happens to implement
io.Reader: encode an empty field and do not invoke methods on the nil receiver.Suggested fix
Check nil pointer/interface values before reader-interface detection. Keep reader handling unchanged for non-nil values.
Add a regression using a typed nil reader whose
Readmethod panics if invoked, and verify multipart encoding completes without a panic and emits an empty field.Impact
This is robustness and request-serialization correctness. A legitimate typed nil value can currently turn request construction into a process panic instead of being handled by the encoder's existing null-value path.