Summary
addFormFieldInputFileUpload in build_request_form.go panics with reflect: call of reflect.Value.IsNil on struct Value when InputFileUpload.Data is an io.Reader whose underlying concrete type is a struct value (not a pointer/chan/map/slice/func/interface).
This is reachable in real code: multipart.File returned by (*multipart.FileHeader).Open() for in-memory parts is a sectionReadCloser struct value (not *sectionReadCloser). Forwarding it directly as InputFileUpload.Data crashes the entire process.
Versions
github.com/go-telegram/bot v1.19.0
- Go 1.22+ (any version, this is a long-standing issue)
Reproduction
Minimal HTTP handler that uploads a small (< 32MB, in-memory) image to SendPhoto:
func upload(b *bot.Bot, w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(10 << 20)
file, header, _ := r.FormFile(\"image\")
defer file.Close()
// PANIC here
b.SendPhoto(r.Context(), &bot.SendPhotoParams{
ChatID: chatID,
Photo: &models.InputFileUpload{Filename: header.Filename, Data: file},
})
}
For files above the multipart in-memory threshold (default 32MB), multipart.FileHeader.Open() returns *os.File (a pointer) and the panic does not occur — which is why this bug only shows up for small files.
Actual panic
panic: reflect: call of reflect.Value.IsNil on struct Value
goroutine N [running]:
reflect.Value.IsNil(...)
github.com/go-telegram/bot.addFormFieldInputFileUpload(...)
github.com/go-telegram/bot.buildRequestForm(...)
github.com/go-telegram/bot.(*Bot).rawRequest.func1(...)
The panic happens inside the goroutine spawned by rawRequest, which has no recover, so the whole process exits.
Root cause
build_request_form.go:103:
func addFormFieldInputFileUpload(form *multipart.Writer, fieldName string, value *models.InputFileUpload) error {
if value.Data == nil || reflect.ValueOf(value.Data).IsNil() {
return fmt.Errorf(\"nil data for field %s\", fieldName)
}
...
}
reflect.Value.IsNil() only accepts kinds Chan, Func, Map, Pointer, UnsafePointer, Interface, Slice. For any other kind it panics. value.Data is io.Reader, whose concrete type can legitimately be a struct value (as sectionReadCloser is).
Suggested fix
Guard the kind before calling IsNil():
func addFormFieldInputFileUpload(form *multipart.Writer, fieldName string, value *models.InputFileUpload) error {
if value.Data == nil {
return fmt.Errorf(\"nil data for field %s\", fieldName)
}
v := reflect.ValueOf(value.Data)
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
if v.IsNil() {
return fmt.Errorf(\"nil data for field %s\", fieldName)
}
}
// struct/array/string/number kinds: not nilable, fall through
...
}
rawRequest's background goroutine should also wrap the body builder in defer recover() so a future panic of any kind doesn't take down the host process — but the kind guard fixes the immediate bug.
Workaround
Wrap the reader in io.LimitReader (or any helper that returns a pointer) before passing it as InputFileUpload.Data:
Photo: &models.InputFileUpload{
Filename: header.Filename,
Data: io.LimitReader(file, header.Size), // *io.LimitedReader, IsNil-safe
}
This is what we ended up shipping after hitting the panic in production with multi-MB user-uploaded images.
Impact
Any project that reads a small file from multipart.FileHeader.Open() (the canonical Go pattern for handling browser uploads) and forwards it to Send{Photo,Document,Audio,...} will crash on every upload. The crash only shows up for files under ~32 MB, so it can hide in development with large test files and only fire in production with smaller user uploads — that's how we found it.
Summary
addFormFieldInputFileUploadinbuild_request_form.gopanics withreflect: call of reflect.Value.IsNil on struct ValuewhenInputFileUpload.Datais anio.Readerwhose underlying concrete type is a struct value (not a pointer/chan/map/slice/func/interface).This is reachable in real code:
multipart.Filereturned by(*multipart.FileHeader).Open()for in-memory parts is asectionReadCloserstruct value (not*sectionReadCloser). Forwarding it directly asInputFileUpload.Datacrashes the entire process.Versions
github.com/go-telegram/botv1.19.0Reproduction
Minimal HTTP handler that uploads a small (< 32MB, in-memory) image to
SendPhoto:For files above the multipart in-memory threshold (default 32MB),
multipart.FileHeader.Open()returns*os.File(a pointer) and the panic does not occur — which is why this bug only shows up for small files.Actual panic
The panic happens inside the goroutine spawned by
rawRequest, which has norecover, so the whole process exits.Root cause
build_request_form.go:103:reflect.Value.IsNil()only accepts kindsChan,Func,Map,Pointer,UnsafePointer,Interface,Slice. For any other kind it panics.value.Dataisio.Reader, whose concrete type can legitimately be a struct value (assectionReadCloseris).Suggested fix
Guard the kind before calling
IsNil():rawRequest's background goroutine should also wrap the body builder indefer recover()so a future panic of any kind doesn't take down the host process — but the kind guard fixes the immediate bug.Workaround
Wrap the reader in
io.LimitReader(or any helper that returns a pointer) before passing it asInputFileUpload.Data:This is what we ended up shipping after hitting the panic in production with multi-MB user-uploaded images.
Impact
Any project that reads a small file from
multipart.FileHeader.Open()(the canonical Go pattern for handling browser uploads) and forwards it toSend{Photo,Document,Audio,...}will crash on every upload. The crash only shows up for files under ~32 MB, so it can hide in development with large test files and only fire in production with smaller user uploads — that's how we found it.