Search code examples
expressdownloadcontent-typemimecloud9

Why do some invalid MIME types trigger a "TypeError," and other invalid MIME types bypass the error and trigger an unprompted download?


I'm making a fairly simple Express app with only a few routes. My question isn't about the app's functionality but about a strange bit of behavior of an Express route.

When I start the server and use the /search/* route, or any route that takes in a parameter, and I apply one of these four content-types to the response:

  • res.setHeader('content-type', 'plain/text');
  • res.setHeader('content-type', 'plain/html');
  • res.setHeader('content-type', 'html/plain');
  • res.setHeader('content-type', 'html/text');

the parameter is downloaded as a file, without any prompting. So using search/foobar downloads a file named "foobar" with a size of 6 bytes and an unsupported file type. Now I understand that none of these four types are actual MIME types, I should be using either text/plain or text/html, but why the download? These two MIME types behave like they should, and the following MIME types with a type but no subtype all fail like they should, they all return an error of TypeError: invalid media type:

  • res.setHeader('content-type', 'text');
  • res.setHeader('content-type', 'plain');
  • res.setHeader('content-type', 'html');

Why do some invalid types trigger an error, and other invalid types bypass the error and trigger a download?

What I've found out so far:

I found in Express 4.x docs that res.download(path [, filename]) transfers the file at path as an “attachment,” and will typically prompt the user for the download, but this download is neither prompted nor intentional.

I wasn't able to find any situation like this in the Express docs (or here on SO) where running a route caused a file to automatically download to your computer.

At first I thought the line res.send(typeof(res)); was causing the download, but after commenting out lines one at a time and rerunning the server, I was able to figure out that only when the content-type is set to 'plain/text' does the download happen. It doesn't matter what goes inside res.send(), when the content-type is plain/text, the text after /search/ is downloaded to my machine.

Rearranging the routes reached the same result (everything worked as it should except for the download.)

The app just hangs at whatever route was reached before /search/foo, but the download still comes through.

My code:

'use strict';
var express = require('express');
var path = require('path');

var app = express();

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname+'/index.html'));
});


app.get('/search', function(req,res){
  res.send('search route');
});
app.get('/search/*', function(req, res, next) {
  res.setHeader('content-type', 'plain/text');
  var type = typeof(res);
  var reqParams = req.params;
  res.send(type);
});


var server = app.listen(process.env.PORT || 3000, function(){
  console.log('app listening on port ' + process.env.PORT + '!');
});

module.exports = server;

Other Details

  • Express version 4.15.2
  • Node version 4.7.3
  • using Cloud9
  • am Express newbie
  • my repo is here, under the branch "so_question"

Solution

  • Why do some invalid types trigger an error...

    Because a MIME-type has a format it should adhere to (documented in RFC 2045), and the ones triggering the error don't match that format.

    The format looks like this:

    type "/" subtype *(";" parameter)
    

    So there's a mandatory type, a mandatory slash, a mandatory subtype, and optional parameters prefixed by a semicolon.

    However, when a MIME type matches that format, it's only syntactically valid, not necessarily semantically, which brings us to the second part of your question:

    ...and other invalid types bypass the error and trigger a download?

    That follows from that is written in RFC 2049:

    Upon encountering any unrecognized Content-Type field, an implementation must treat it as if it had a media type of "application/octet-stream" with no parameter sub-arguments. How such data are handled is up to an implementation, but likely options for handling such unrecognized data include offering the user to write it into a file (decoded from its mail transport format) or offering the user to name a program to which the decoded data should be passed as input.

    (emphasis mine)