← Quay lại
10 tháng 12, 2025
•
4 phút đọc
•
Ký tự đặc biệt trong header Content-Disposition
TL;DR
- Lỗi: Không tải được file từ S3, trả về lỗi XML InvalidArgument liên quan đến Header.
- Thủ phạm: Tên file chứa dấu cách full-width (dấu cách trong bộ gõ tiếng Nhật/Trung/Hàn), ví dụ: "ABC .xlsx".
- Nguyên nhân: Header Content-Disposition mặc định dùng chuẩn ISO-8859-1 cũ, không hiểu được ký tự UTF-8.
- Giải pháp: Sử dụng chuẩn RFC 5987, chuyển sang dùng tham số filename* kết hợp với URL Encoding.
---
"Alo em, khách báo lỗi!"
Vào một ngày đẹp trời, PM nhắn mình: "Ken ơi (trên công ty mình tên Ken 🤓), khách hàng mail tao là nó không tải file này được? Chức năng này chạy ngon ơ xưa giờ mà?".
Á à, kèo thơm thì tìm người khác kèo thúi thì Ken ơi ha. À mà dự án có mỗi mình là dev, không Ken thì ai nữa! Thế là mình xin ngay ảnh chụp màn hình và link file để check. Thử nhấp vào link có tên là ABC nằm mơ đ .xlsx thì... Bùm! Không tải được thật. Trình duyệt đập vào mặt một tin lỗi từ AWS S3:
<Error>
<Code>InvalidArgument</Code>
<Message>
Header value cannot be represented using ISO-8859-1.
</Message>
<ArgumentName>response-content-disposition</ArgumentName>
<ArgumentValue>attachment; filename="ABC nằm mơ đ .xlsx"</ArgumentValue>
...
</Error>Nội dung lỗi khá rõ ràng: Giá trị của Header Content-Disposition không thể biểu diễn bằng bảng mã ISO-8859-1.
Truy tìm thủ phạm
Các bạn có nhận thấy điều gì lạ ở tên file ABC nằm mơ đ .xlsx không? Nhìn kỹ vào khoảng trắng sau chữ "đ" xem, sao nó rộng toang hoác thế kia!!? Mình copy tên file bỏ vào editor và soi kỹ. Hóa ra, đây không phải dấu cách (space) bình thường chúng ta hay gõ mà là Full-width Space, thường xuất hiện khi người dùng nhập bằng kiểu chữ tiếng Nhật, Hàn hoặc Trung.
Chắc mấy khứa rảnh muốn thử thách thằng dev quèn này nên mới nhét chúng vào đây mà. Không sao, dev tuy quèn nhưng còn lâu mới chịu thua ba cái đồ yêu này.
Truy tìm nguyên nhân
Để hiểu tại sao S3 trả về lỗi chúng ta cần quay ngược thời gian một chút về chuẩn HTTP.
Ngày xửa ngày xưa, Internet chủ yếu dùng bảng mã ISO-8859-1 (Latin-1) cho các HTTP Header. Bảng mã này chỉ hỗ trợ các ký tự phương Tây cơ bản. Nó không hề biết đến các ký tự tiếng Việt (đ, ê, ơ...) hay các ký tự Full-width "béo phì" của châu Á.
Khi server cố gắng nhét dấu cách full-width kia vào header Content-Disposition đã làm cho S3 bị "nghẹn" vì ký tự đó không nằm trong bảng mã cho phép.
Vậy làm sao để hỗ trợ UTF-8 trong Header? Đó cũng là điều mà các kỹ sư thông minh hơn mình từ lâu đã nghĩ tới và đã tạo ra một chuẩn mới là RFC 5987. Chuẩn này cho phép ta nói với server là tôi muốn dùng mã hoá UTF-8 bằng cách dùng cú pháp filename* (chú ý dấu sao * nhé). Mình sẽ minh hoạ nó bằng một ví dụ:
filename*=UTF-8''t%C3%AAn%20file%20m%E1%BB%9Bi.xlsx
Trong đó:
- UTF-8: Chỉ định bảng mã.
- '': Cặp dấu nháy đơn để trống (thường dùng để chỉ định ngôn ngữ, nhưng ta có thể bỏ qua).
- Phần còn lại: Tên file đã được URL Encoded.
Fix lỗi
Dự án mình dùng Ruby on Rails và gem aws-s3. Để S3 hiểu và trả về header đúng chuẩn RFC 5987, mình cần áp dụng cú pháp trên khi tạo Presigned URL. Đổi filename= thành filename*= kết hợp với ERB::Util.url_encode để mã hóa tên file. Đây là đoạn code sửa lỗi:
# Mã hóa tên file để trình duyệt và S3 đều hiểu
encoded_filename = ERB::Util.url_encode(filename)
# Tạo options cho S3 presigned URL
opts = {
expires_in: 15.minutes,
response_content_disposition: "attachment; filename*=UTF-8''#{encoded_filename}"
}
# Tạo URL
url = s3_object.presigned_url(:get, opts)Kết quả: Khi người dùng bấm tải về, S3 trả về header:
Content-Disposition: attachment; filename*=UTF-8''ABC%20n%E1%BA%B1m%20m%C6%A1%20%C4%91%E3%80%80.xlsx
Trình duyệt nhìn thấy filename* sẽ tự động decode lại ra chữ tiếng Việt và dấu cách full-width. Khách hàng vui vẻ, PM hết càm ràm, và mình lại ngồi làm ly matcha cold whisk mát lành.🍵
Lời kết
Chỉ một dấu cách cũng đủ làm sập tính năng download. Qua bug này, mình học được rằng: Đừng bao giờ tin tưởng input của người dùng, kể cả tên file! Và thêm nữa còn được tìm hiểu Content-Disposition để xử lý các file có tên tiếng Việt hoặc ký tự đặc biệt.
Happy coding!