The Content-Disposition header generated by ContentDisposition.toString() includes either the regular filename
parameter or the extended filename*
parameter, depending on the value of the charset
attribute. For backwards compatibilities with older browser it would be preferable to always include the regular parameter, in addition to the extended parameter. This also seems to be what RFC 6266 suggests:
RFC 6266, section 4.3: Many user agent implementations predating this specification do not understand the "filename" parameter. Therefore, when both "filename" and "filename" are present in a single header field value, recipients SHOULD pick "filename" and ignore "filename". This way, senders can avoid special-casing specific user agents by sending both the more expressive "filename" parameter, and the "filename" parameter as fallback for legacy recipients (see [Section 5] for an example).
RFC 6266, section 5 (Examples): This example is the same as the one above, but adding the "filename" parameter for compatibility with user agents not implementing [RFC 5987]:
Content-Disposition: attachment;
filename="EURO rates";
filename*=utf-8''%e2%82%ac%20rates
Note: Those user agents that do not support the [RFC 5987] encoding ignore "filename*" when it occurs after "filename".
Comment From: poutsma
While having both parameters sounds like a good idea, I am having difficulty seeing how we can transform any given String into an US-ASCII string for the filename
parameter. For instance, given the filename € rates
, I don't see how we can transliterate €
into EUR
, as seen in the example.
Comment From: arjohn-telecats
Transliterating this is way out of scope for the ContentDisposition class imho. Options that might work for the regular attribute: 1) Simply url-encode any non-ascii characters (using utf8?) and hope that the browser decodes it in the same way. 2) Replace any non-ascii characters with a replacement character, an underscore for example. 3) Extended the API and allow developers to set both attributes separately.
The first option works for me with the latest Chrome and Firefox releases.
Comment From: poutsma
I decided to resolve this by encoding any non-ASCII characters as quoted-printable (RFC 2047), as that seems to be the most common way to do so. Moreover, we already had decoding support for it.
Comment From: arjohn-telecats
RFC 2047 is related to MIME/email. Are you sure that this also applies to HTTP? I've never seen a quoted printable HTTP header, nor was I able to find any references to this on the internet. URL-encoding seems to be the commonly used method for filename parameters. Perhaps a quick test to check how a browser treats quoted-printable encoding might be in order.
Comment From: poutsma
As a general remark, non-ASCII filename
encodings in Content-Disposition are a mess. Or at least they were, before RFC 5987.
That said, RFC 2047 does apply filenames in HTTP Content-Disposition fields. It is supported by Commons File Upload, Firefox, and Chrome. Granted, browsers are looking to drop support in favor of RFC 5987, but it's still supported, also by Spring Framework.
In Content-Disposition::parse
, we already support RFC 2047, 5987, and Base64 filenames. So it made sense to use either quoted printable or base 64 for encoding the filename
parameter, otherwise we had to introduce a fourth parsing mechanism as well, or the result of toString
would not be parsable by parse
. I decided to use RFC 2047 because its results are more readable than Base64.
Comment From: mariuszpala
Hi, we have exactly the same issue after upgrading to Spring Boot 3.2.3, although your explanation seems OK, but the browser is no longer working with file names.
This the the Content-Disposition header being returned:
attachment; filename="=?UTF-8?Q?dev=5Fdocument.xlsx?="; filename*=UTF-8''dev_document.xlsx
And the this is how latest Chrome version handles it:
It was working just fine with Spring Boot 2.7.18 which was returning the header below:
attachment; filename*=UTF-8''dev_document.xlsx
This is the code we use - any clue how to resolve it?
String contentDisposition = ContentDisposition.attachment()
.filename("dev_document"xlsx", StandardCharsets.UTF_8)
.build()
.toString();