loading...

Had to use browser detection like its 1999

kspeakman profile image Kasey Speakman Updated on ใƒป4 min read

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.

Chapter 1: File.type cannot be trusted

So I make a way for a user to upload a file. I plop down a file input on a web page. The user selects a file. In Javascript I pull the file off the input element and upload it with an XHR. As part of sending it I also send the file name, size, and file type.

The JS 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?

Chapter 2: But IE

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.

Chapter 3: I Did the Bad Thing

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. ๐Ÿ˜€

Posted on by:

kspeakman profile

Kasey Speakman

@kspeakman

collector of ideas. no one of consequence.

Discussion

markdown guide
 

Nice writeup, we hear you.

Perhaps you could have saved a little bit of time by using a ready made plugin such as danml.com/download.html or perhaps this github.com/johnculviner/jquery.fil.... They might be doing the same browser detection under the hood but we work faster with a little help from the greater development community. ;-)

 

Thanks for the comment. The linked packages appear to be for browser-side. Whereas the article is focused on browser detection on the server side so that downloads behave properly for the user.

 

Sorry
kind of skimmed through it and looked like you were talking about a client side struggle. Still surprised this kind of endeavour does not have a dedicated library to it.

 

User agent for public beta Chromium-based Edge is just Chrome

 

Couldn't you check mime type on server side then send proper mime type to content type header? Or I'm missing some part of the story...

 

Good question. In the post I jokingly said that it's just easier not to do things. But I will give actual reasons.

MIME types and file extensions serve exactly the same purpose. However, MIME types are gone after the transfer is done. But sitting on the user's hard drive, the file extension will still continue to indicate the type of file and what program opens it.

To provide the MIME type, I would have to bring in a dependency (and a chunk of memory) to map the file extension to MIME type. And it's redundant information since the browser and/or OS already know how to handle file extensions.

MIME types would be useful in cases where I was streaming amorphous data. Or in cases where I am using non-standard or absent file extensions to transmit data, especially if it is meant to be displayed in browser or email. For example, serving a webpage without .html on the end. Common example GET somesite.com/about

I referred to MIME Type as Media Type in my post. Since the W3C decided they didn't like "MIME"s.