Content-Disposition headers in .NET


23 August 2011, by

When serving file downloads from ASP.NET one issue that often gets overlooked is correctly encoding the Content-Disposition header. You’ll frequently see the following code snippet on blogs and programming forums:

Response.Clear();
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);
Response.BinaryWrite(fileContents);
Response.End();

The Content-Disposition header, as specified in RFC 2183, distinguishes between files served for download and files to be displayed by the browser. It’s also frequently used to specify the filename for a downloaded file and can include extra information such as the file’s date and time.

The Problem

The error, on the highlighted line, is that the filename should be escaped (RFC 2184) for inclusion in the header – if, for example, the filename has a space in it then either the whole filename should be quoted (and quotes in the string escaped)

Content-Disposition: attachment; filename="Q1 Report.pdf"

or the entire filename encoded using the scheme specified in RFC 2231, which uses %20 to represent a space:

Content-Disposition: attachment; filename*=UTF-8''Q1%20Report.pdf

This all said, the code above will usually work: the error is common enough that some browsers (such as Internet Explorer and Chrome) will still process a Content-Disposition header filename that contains spaces even if it is unquoted and not encoded – but others, notably Firefox, will not. In some applications the filenames here may be under a user’s control, e.g. serving back uploaded files with their original file names, and so may well contain spaces or other special characters.

A Partial Solution

Now .NET has a built-in class to handle this, System.Net.Mime.ContentDisposition, e.g.

var contentDispositionHeader = new ContentDisposition() { FileName = filename };
Response.AddHeader("Content-Disposition", contentDispositionHeader.ToString());

but unfortunately it only does half the job: it will always generate the first quoted form above and not the second, RFC 2231-encoded form. This second form is needed if the filename contains non-ASCII characters; if it does, then the AddHeader call will throw an exception.

This means than if you are using ASP.NET forms or the first release of ASP.NET MVC then you also need to detect non-ASCII filenames and generate the RFC 2231-encoded filename yourself. The simple code snippet above has become much more complicated!

A Full Solution – but only if you’re using ASP.NET MVC

Thankfully in ASP.NET MVC 2 or later there is a built-in code to handle this for you; the File method inherited from the base controller class

return File(data, contentType, filename);

will manage the whole download for you, including generating the Content-Disposition header correctly for non-ASCII character filenames.

Security

A final note about security – as with any situation where a user-supplied string is incorrectly encoded there’s a potential security issue here! For example, if you had free rein to inject text into HTTP headers you could name a file

Innocent_sounding_file<line break>
Content-Length: 26<line break>
<line break>
Malicious File Contents!<line break>
<line break>

effectively injecting fake, alternative file contents for any user that downloads this file, all through the Content-Disposition header. Fortunately this is not an issue in .NET: the whole header will be safely encoded according to RFC 2184 – you’ll end up with a file download called “Innocent_sounding_file%0D%0AContent-Length%2E” etc. Why encode this automatically but not the Content-Disposition header filename? It would need to accept a list of key-value pairs to assemble into a header; the current version of the AddHeader API instead accepts a single string value and assumes you have performed any necessary assembling yourself.

Tags: ,

Categories: Technical

«
»

3 Responses to “Content-Disposition headers in .NET”

  1. Ross Presser says:

    As of .NET 4.5 (possibly earlier) the System.Net.Mime.ContentDisposition class will generate dispositions of this form:

    Content-Disposition: attachment; filename=”=?utf-8?B?Zm9vLcOkLmh0bWw=?=”

    when there are non-ASCII characters in the filename. (This example encodes the filename “foo-ä.html” )

    This is also legal according to RFC 2231.

  2. Rupert Wood says:

    Thanks! I’m afraid that was long enough ago that I don’t remember whether it was .NET 3.5 or 4.0 that only generated the quoted form, not the RFC 2231 form, but glad to hear they’ve fixed the framework class.

  3. Oskar says:

    Using System.Net.Mime.ContentDisposition or File(data, contentType, filename) (on ASP.Net MVC 3) will NOT generate standards conforming values when running on .Net 4.5. In .Net 4.0, ContentDisposition.ToString() threw a FormatException when non-US-ASCII characters were present – System.Web.Mvc.FileResult relied on that exception to switch to proper RFC2231 (RFC5987) encoding. In .Net 4.5 that FormatException was removed, causing web applications using ASP.Net MVC 3 to generate illegal Content-Disposition HTTP headers. This seems to have been fixed in later MVC versions, but I’m not sure which.

    Background below (mostly because I wrote is before I realised what was really going on):

    The similar-to-RFC2047 syntax:
    attachment; filename=”=?utf-8?B?YcO2YQ==?=”
    as generated by System.Net.Mime.ContentDisposition is NOT legal according to RFC2047 and RFC2231, as I understand it.

    This looks sort of like RFC2047 encoding, but that RFC, page 8 expressly states that an encoded word cannot appear in a quoted-string, and also (same page) that “An ‘encoded-word’ MUST NOT be used in parameter of a MIME Content-Type or Content-Disposition field…”. So it’s invalid.

    RFC2231 extends RFC2047 to allow encoding also in parameter values but explicitly states that “It is clear that a distinguished parameter name is needed to identify when this information is present”, and proceeds to describe a syntax (note the asterisk and the lack of double quotes):
    foo; anotherParam*=utf-8”a%c3%b6a

    Note that both RFC2047 and RFC2231 applies to MIME headers and not to HTTP.

    RFC5987 describes how RFC2231 applies to HTTP in general, by removing unneeded features and making iso-8859-1 and utf-8 mandatory.

    Finally, RFC6266 updates the definition of the Content-Disposition header for use in HTTP and defines the parameters “filename” (which should still always be iso-8859-1) and “filename*”, which should always be encoded according with the scheme defined in RFC5987, to arrive at this syntax:
    attachment; filename=”aöa”; filename*=utf-8”a%c3%b6a

    The test suite for RFC6266 (http://greenbytes.de/tech/tc2231/), specifically case attrfc2047token and attrfc2047quoted also points out that it is an error if the browser decodes RFC2047 data for the filename.


Leave a Reply

* Mandatory fields


+ seven = 16

Submit Comment