0

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"
lift-it-luke
  • 407
  • 1
  • 6
  • 11

2 Answers2

2

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)

robertklep
  • 198,204
  • 35
  • 394
  • 381
  • There it is! I had noticed "application/octet-stream" in documentation I was reading, and thought that some sort of guesswork was being done about what type was being returned, but this was the piece I was missing. Thank you so much. – lift-it-luke May 13 '17 at 22:01
0

The order in which you define your routes matters a lot in express, you probably need to move your default '/' route to be after the '/search/*' route.

  • I've tried rearranging the routes, any combination works like it should but still causes the download so long as the setHeader line is present. Any idea why that is? – lift-it-luke May 13 '17 at 15:52
  • Nope sorry, why plain/text rather than 'text/html'. Also could it be 'text/plain' rather than 'plain/text'? – Patrick Nouvion May 13 '17 at 16:27
  • https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types – Patrick Nouvion May 13 '17 at 16:31
  • It could be 'text/plain', or even 'text/html'. But those behave normally, they don't trigger the download. Clearly it has something to do with the MIME type like you suggest. Actually I don't need to set the header at all for my project, `res.json()` covers most of what I need. I was just trying out different content-types and was curious about the download, why some return a `TypeError: invalid media type' and some just download the route parameter as a file. – lift-it-luke May 13 '17 at 20:13