Web Security: Forcing browsers to download a file instead of previewing it

Gaurav Gupta
4 min readJul 4, 2019
Photo by Dayne Topkin on Unsplash

As part of our application functionality, we have to allow users to export data in pdf or excel format. The first challenge was to enable downloading files behind authentication. Even after that was done, there was still one interesting aspect to this downloading functionality.

You must have experienced that sometimes (but not always)the browser opens/previews a pdf file instead of downloading it, given that the browser has a pdf viewer plugin, and you have allowed it access. Why does that happen, and more importantly why doesn’t that happen all the time? Does it depend on the browser? or the server? Either way, wouldn’t you like it if your pdfs were always previewed directly instead of getting downloaded (and the user can then download them if required). Let’s try to answer these questions.

How it works?

The server can indicate to the client whether to preview a file or download it. Based on this indication and its own capabilities and user permissions, the client can decide whether or not it can preview it.

All of this is decided based on the Content-Disposition header. Loosely speaking, it can take 2 values attachmentor inline . If the server specifies attachment, it is a command to the browser to download the file instead of executing/previewing it. If the server specifies inline, the browser checks whether or not it has the capability to execute/preview that file , or else it will just open the File Save dialog.

The Content-Type header also plays an important role. When Content-Disposition is inline , the browser tries to preview the file based on the Content-Type header, so if you are sending a pdf file and you want it to be previewed, but you send the Content-Type as “image/png” , the browser would just preview it (incorrectly) as a blank image.

If the Content-Type header is missing, the browser resorts back to mime-sniffing and based on that decides how to preview the file. So if you do not send a Content-Type header at all when sending a pdf file and Content-Disposition is inline, chances are that the browser would be able to guess the mime type correctly and preview the pdf appropriately.

If the Content-Disposition header is missing, the browser resorts back to Content-Type for deciding what to do with the file, which effectively means it treats the request as having Content-Disposition:inline.

So, how do we decide whether we want the user to preview a file or download it?
It depends on application functionality. If you expect the user to already have the file, then it might make sense to allow the browser to preview it inline, for pdfs it might always make sense to preview inline, based on which user can decide whether or not to save it. Or you may consider the file to be one time use, so you don’t want the user to go through the effort of saving it in their filesystem and then opening it. If you are expecting the user to download multiple files, it makes sense to not preview it and directly save it to file system.

Ok, so what about security then?

As we have already seen that the browser has the capability to preview a file. What happens if this file is a html file ? The browser would treat it as any other html file originating from the same origin and would execute any javascript inside it. This effectively means, if someone uploaded an html file to your application and somebody else downloaded that html file, the html file would be executed in the context of your application and would have access to everything that your regular application files have.

How do you tackle this?

Spend some time and effort in sending an appropriate Content-Disposition header for all the different type of files that you allow a user to download, or better, do not allow uploading files except for a white-list of extensions.

You could also server user generated content from a different domain than the one your site is running on.

References:

--

--