Today, I regretfully confess that I used browser detection for some back-end code. In this installment of "Why IE (and Edge) are Terrible" I will chronicle the no-fun-at-all, 30-browser-tabs-open-to-research, nuances of file downloading.
File object has a property called
type that contains the media type of the file. Do not rely on it. Because at some point IE will leave it blank. Like when I uploaded a .docx file, it was not set... even though this is used by Word... a Microsoft program. Whereas Chrome accurately came up with the inordinately long media type for newer Word files. Go figure.
So now I have to figure out how to come up with a media type, so I can tell the browser that info (with the Content-Type header) when the files are later downloaded. I can try to calculate the media type based on the extension. I even found this repo which does that. But then I thought: it is easier to not do things. So I decided to just avoid media types altogether. When someone downloads the file, the browser or OS can decide based on the file extension like they always have. They are already tracking which extensions open which programs, so why not?
So of course like all of you, I get it working in Chrome (or rather Brave, but same thing). I implement the download without sending the Content-Type header and it works just like you would think it should. When you click the download link, it will open in-browser for recognized file extensions like PDF, but otherwise will prompt to download. And if you right-click and Save As it will correctly prompt you with the file name from the URL. You might think this is an obvious thing to point out, and so would I. But oooooh just you wait and see what IE does. I'm proud of myself because I got it done in short order. Then as I'm about to close out and start on something else, I happen to think "I'll check it in IE real quick." That thought usually precedes wasted hours, and this occasion was no exception.
So IE (and Edge) has the insane idea that if there is no Content-Type, then let's assume it is HTML. Oh, it has a file extension that I know means something else which is not HTML? Don't care, ignore it, it's still HTML to me (speaking as IE/Edge... IEdge?). Anyway when the user clicks on the link, IEdge will attempt to display the raw contents of the file in browser. I love a good screen full of gibberish as much as the next person. But I expect my users will be confused by this. And worse they will call to ask about it. And I hate it when my phone rings. But wait, there's one more thing to test. What happens when I right-click Save As? In the dialog box, it uses the file name from the URL but replaces the file extension with
.html. The user literally has to change the File Type dropdown to "All Files" and then manually change the extension back to what is already there in the URL. That seems like a fun thing to do as a user, right? I get a glimpse of the future and it is bleak... because my phone is ringing.
Experimentally, I discovered that I can make IEdge behave in a sane way with downloads (known as the default in the other browsers) if I send a Content-Type of "application/octet-stream". Great, I will just always send that. Not so fast! I discovered that in non-IEdge browsers, when they receive an octet stream they will only prompt the user to download the file. They will not try to open recognized extensions. Drat!
So I decided to sniff the browser. Ugh, that sounds disgusting. Can you imagine the rot that IE has by now? With some research, I figured out the bare minimum things to check.
When the User-Agent header contains:
MSIE- it is IE 10 and below
Trident- IE 11 (and a couple other IE versions)
Edge- that's, um, Edge
- ???? - Your move, Chromium-based Edge.
More importantly, other browser User-Agent strings do not contain these terms. So a simple
contains check will do. Whenever I see one of these in the User-Agent header, I set the response Content-Type to be an octet stream. Otherwise I leave it off. While I'm at it, I send an extra header for IEdge. Looks like this.
X-Stop-Using: IE (or Edge as the case may be). I figure it is only fair to waste a few microseconds bandwidth on this invisible nag, considering my wasted time and code for the nonsense way that IEdge handles this (and many other things).
If you notice any gotchas with this solution, I would appreciate you pointing it out. I am not being sarcastic! I promise not to tease your response as mercilessly as I have IE and Edge in this post.
I seriously do hate it when my phone rings. And this is why our dev team also does support. Because if it rings enough for the same reason, we will change the code to make it stop. 😀