Fix S3 filename header escaping

Use an ASCII-only fallback for Content-Disposition filename and
RFC 5987 path escaping for filename* so presigned S3 responses keep
spaces and Unicode filenames interoperable.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Ludovic <ludovic@probo.com>
This commit is contained in:
Cursor Agent
2026-06-11 14:12:01 +00:00
parent 732493d054
commit 6794f14a24
2 changed files with 56 additions and 3 deletions

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"io"
"net/url"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
@@ -118,11 +119,10 @@ func (s *Service) GeneratePresignedURL(
) (string, error) {
presignClient := s3.NewPresignClient(s.s3Client)
encodedFilename := url.QueryEscape(file.FileName)
contentDisposition := fmt.Sprintf(
"attachment; filename=%q; filename*=UTF-8''%s",
encodedFilename,
encodedFilename,
asciiFilename(file.FileName),
url.PathEscape(file.FileName),
)
presignedReq, err := presignClient.PresignGetObject(
@@ -145,6 +145,22 @@ func (s *Service) GeneratePresignedURL(
return presignedReq.URL, nil
}
func asciiFilename(filename string) string {
var b strings.Builder
b.Grow(len(filename))
for _, r := range filename {
if r < 0x20 || r > 0x7e {
b.WriteByte('_')
continue
}
b.WriteRune(r)
}
return b.String()
}
// GetFileSize determines the byte size of a seekable io.Reader by seeking to
// the end and back. Returns an error if content is not seekable.
func GetFileSize(content io.Reader) (int64, error) {