fix(cabana): answer an empty or malformed avatar upload body with 422

- POST /account/avatar with no multipart part (NextPart io.EOF) or a broken
  boundary fell through to the generic 500; it is now the invalid-body 422
- a body over the cap still answers 413 payload_too_large
This commit is contained in:
2026-10-09 17:53:21 +02:00
parent a03302e8fe
commit 4f1cda33d7

View File

@@ -587,7 +587,7 @@ func (s *service) avatarUpload(w http.ResponseWriter, r *http.Request) {
}
part, err := mr.NextPart()
if err != nil {
s.writeAvatarError(w, r, body, err)
s.writeAvatarError(w, r, body, multipartFailure(body, err))
return
}
if part.FormName() != "file_data" || strings.TrimSpace(part.FileName()) == "" {
@@ -614,7 +614,7 @@ func (s *service) avatarUpload(w http.ResponseWriter, r *http.Request) {
if err == nil {
return invalidBody()
}
return err
return multipartFailure(body, err)
}
q := tx.Session(&gorm.Session{NewDB: true, Context: ctx})
if err := q.Model(&attach.File{}).Where("id = ?", f.ID).Updates(map[string]any{
@@ -680,6 +680,17 @@ func (s *service) avatarRemove(w http.ResponseWriter, r *http.Request) {
WriteData(w, http.StatusOK, s.profileFor(r.Context(), db, saved), map[string]any{})
}
// multipartFailure is the error of a multipart read: a body past the cap
// stays a *http.MaxBytesError (413), and an empty or malformed body (no
// part at all, a broken boundary) is the invalid-body 422, never a 500.
func multipartFailure(body *bodyReader, err error) error {
var tooBig *http.MaxBytesError
if errors.As(err, &tooBig) || (body != nil && errors.As(body.err, &tooBig)) {
return err
}
return invalidBody()
}
// writeAvatarError maps an avatar upload failure as writeFileError does,
// with the field name avatar: a body past the cap is 413, an attach.Store
// refusal a 422 on avatar and a malformed multipart body a 422 on body.