Skip to content

Commit a24fd0c

Browse files
chillypepperdougwilson
authored andcommitted
Add options to res.download
closes #3327 closes #3370
1 parent 95fb5cc commit a24fd0c

File tree

2 files changed

+110
-4
lines changed

2 files changed

+110
-4
lines changed

lib/response.js

+30-4
Original file line numberDiff line numberDiff line change
@@ -515,30 +515,56 @@ res.sendfile = deprecate.function(res.sendfile,
515515
* when the data transfer is complete, or when an error has
516516
* ocurred. Be sure to check `res.headersSent` if you plan to respond.
517517
*
518-
* This method uses `res.sendfile()`.
518+
* Optionally providing an `options` object to use with `res.sendFile()`.
519+
* This function will set the `Content-Disposition` header, overriding
520+
* any `Content-Disposition` header passed as header options in order
521+
* to set the attachment and filename.
522+
*
523+
* This method uses `res.sendFile()`.
519524
*
520525
* @public
521526
*/
522527

523-
res.download = function download(path, filename, callback) {
528+
res.download = function download (path, filename, options, callback) {
524529
var done = callback;
525530
var name = filename;
531+
var opts = options || null
526532

527-
// support function as second arg
533+
// support function as second or third arg
528534
if (typeof filename === 'function') {
529535
done = filename;
530536
name = null;
537+
opts = null
538+
} else if (typeof options === 'function') {
539+
done = options
540+
opts = null
531541
}
532542

533543
// set Content-Disposition when file is sent
534544
var headers = {
535545
'Content-Disposition': contentDisposition(name || path)
536546
};
537547

548+
// merge user-provided headers
549+
if (opts && opts.headers) {
550+
var keys = Object.keys(opts.headers)
551+
for (var i = 0; i < keys.length; i++) {
552+
var key = keys[i]
553+
if (key.toLowerCase() !== 'content-disposition') {
554+
headers[key] = opts.headers[key]
555+
}
556+
}
557+
}
558+
559+
// merge user-provided options
560+
opts = Object.create(opts)
561+
opts.headers = headers
562+
538563
// Resolve the full path for sendFile
539564
var fullPath = resolve(path);
540565

541-
return this.sendFile(fullPath, { headers: headers }, done);
566+
// send file
567+
return this.sendFile(fullPath, opts, done)
542568
};
543569

544570
/**

test/res.download.js

+80
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,86 @@ describe('res', function(){
7171
})
7272
})
7373

74+
describe('.download(path, filename, options, fn)', function () {
75+
it('should invoke the callback', function (done) {
76+
var app = express()
77+
var cb = after(2, done)
78+
var options = {}
79+
80+
app.use(function (req, res) {
81+
res.download('test/fixtures/user.html', 'document', options, done)
82+
})
83+
84+
request(app)
85+
.get('/')
86+
.expect(200)
87+
.expect('Content-Type', 'text/html; charset=UTF-8')
88+
.expect('Content-Disposition', 'attachment; filename="document"')
89+
.end(cb)
90+
})
91+
92+
it('should allow options to res.sendFile()', function (done) {
93+
var app = express()
94+
95+
app.use(function (req, res) {
96+
res.download('test/fixtures/.name', 'document', {
97+
dotfiles: 'allow',
98+
maxAge: '4h'
99+
})
100+
})
101+
102+
request(app)
103+
.get('/')
104+
.expect(200)
105+
.expect('Content-Disposition', 'attachment; filename="document"')
106+
.expect('Cache-Control', 'public, max-age=14400')
107+
.expect('tobi')
108+
.end(done)
109+
})
110+
111+
describe('when options.headers contains Content-Disposition', function () {
112+
it('should should be ignored', function (done) {
113+
var app = express()
114+
115+
app.use(function (req, res) {
116+
res.download('test/fixtures/user.html', 'document', {
117+
headers: {
118+
'Content-Type': 'text/x-custom',
119+
'Content-Disposition': 'inline'
120+
}
121+
})
122+
})
123+
124+
request(app)
125+
.get('/')
126+
.expect(200)
127+
.expect('Content-Type', 'text/x-custom')
128+
.expect('Content-Disposition', 'attachment; filename="document"')
129+
.end(done)
130+
})
131+
132+
it('should should be ignored case-insensitively', function (done) {
133+
var app = express()
134+
135+
app.use(function (req, res) {
136+
res.download('test/fixtures/user.html', 'document', {
137+
headers: {
138+
'content-type': 'text/x-custom',
139+
'content-disposition': 'inline'
140+
}
141+
})
142+
})
143+
144+
request(app)
145+
.get('/')
146+
.expect(200)
147+
.expect('Content-Type', 'text/x-custom')
148+
.expect('Content-Disposition', 'attachment; filename="document"')
149+
.end(done)
150+
})
151+
})
152+
})
153+
74154
describe('on failure', function(){
75155
it('should invoke the callback', function(done){
76156
var app = express();

0 commit comments

Comments
 (0)