Skip to content

Commit

Permalink
support non-ascii filenames in content-disposition headers
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy VanWagoner committed Apr 20, 2014
1 parent 79cc5a4 commit 56b672e
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 5 deletions.
3 changes: 2 additions & 1 deletion examples/downloads/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var app = module.exports = express();
app.get('/', function(req, res){
res.send('<ul>'
+ '<li>Download <a href="/files/amazing.txt">amazing.txt</a>.</li>'
+ '<li>Download <a href="/files/utf-8 한中日.txt">utf-8 한中日.txt</a>.</li>'
+ '<li>Download <a href="/files/missing.txt">missing.txt</a>.</li>'
+ '</ul>');
});
Expand Down Expand Up @@ -40,4 +41,4 @@ app.use(function(err, req, res, next){
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}
1 change: 1 addition & 0 deletions examples/downloads/files/utf-8 한中日.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
한中日
7 changes: 3 additions & 4 deletions lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var escapeHtml = require('escape-html');
var sign = require('cookie-signature').sign;
var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
var contentDisposition = require('./utils').contentDisposition;
var etag = require('./utils').etag;
var statusCodes = http.STATUS_CODES;
var cookie = require('cookie');
Expand Down Expand Up @@ -365,7 +366,7 @@ res.download = function(path, filename, fn){
}

filename = filename || path;
this.set('Content-Disposition', 'attachment; filename="' + basename(filename) + '"');
this.set('Content-Disposition', contentDisposition(filename));
return this.sendfile(path, fn);
};

Expand Down Expand Up @@ -487,9 +488,7 @@ res.format = function(obj){

res.attachment = function(filename){
if (filename) this.type(extname(filename));
this.set('Content-Disposition', filename
? 'attachment; filename="' + basename(filename) + '"'
: 'attachment');
this.set('Content-Disposition', contentDisposition(filename));
return this;
};

Expand Down
23 changes: 23 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

var mime = require('send').mime;
var crc32 = require('buffer-crc32');
var basename = require('path').basename;

/**
* Return ETag for `body`.
Expand Down Expand Up @@ -84,6 +85,28 @@ exports.normalizeTypes = function(types){
return ret;
};

/**
* Generate Content-Disposition header appropriate for the filename.
* non-ascii filenames are urlencoded and a filename* parameter is added
*
* @param {String} filename
* @return {String}
* @api private
*/

exports.contentDisposition = function(filename){
var ret = 'attachment';
if (filename) {
filename = basename(filename);
// if filename contains non-ascii characters, add a utf-8 version ala RFC 5987
ret = /[^\040-\176]/.test(filename)
? 'attachment; filename=' + encodeURI(filename) + '; filename*=UTF-8\'\'' + encodeURI(filename)
: 'attachment; filename="' + filename + '"';
}

return ret;
};

/**
* Parse accept params `str` returning an
* object with `.value`, `.quality` and `.params`.
Expand Down
31 changes: 31 additions & 0 deletions test/res.attachment.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,35 @@ describe('res', function(){
.expect('Content-Type', 'image/png', done);
})
})

describe('.attachment(utf8filename)', function(){
it('should add the filename and filename* params', function(done){
var app = express();

app.use(function(req, res){
res.attachment('/locales/日本語.txt');
res.send('japanese');
});

request(app)
.get('/')
.expect('Content-Disposition', 'attachment;' +
' filename=%E6%97%A5%E6%9C%AC%E8%AA%9E.txt;' +
' filename*=UTF-8\'\'%E6%97%A5%E6%9C%AC%E8%AA%9E.txt',
done);
})

it('should set the Content-Type', function(done){
var app = express();

app.use(function(req, res){
res.attachment('/locales/日本語.txt');
res.send('japanese');
});

request(app)
.get('/')
.expect('Content-Type', 'text/plain; charset=utf-8', done);
})
})
})

0 comments on commit 56b672e

Please sign in to comment.