Skip to content

Commit 0def9bb

Browse files
mmitodougwilson
authored andcommitted
Add "root" option to res.download
fixes #4834 closes #4855
1 parent 4847d0e commit 0def9bb

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

Diff for: History.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
unreleased
22
==========
33

4+
* Add "root" option to `res.download`
45
* Deprecate string and non-integer arguments to `res.status`
56
* Ignore `Object.prototype` values in settings through `app.set`/`app.get`
67
* Support proper 205 responses using `res.send`

Diff for: lib/response.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,9 @@ res.download = function download (path, filename, options, callback) {
582582
opts.headers = headers
583583

584584
// Resolve the full path for sendFile
585-
var fullPath = resolve(path);
585+
var fullPath = !opts.root
586+
? resolve(path)
587+
: path
586588

587589
// send file
588590
return this.sendFile(fullPath, opts, done)

Diff for: test/res.download.js

+74
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
var after = require('after');
44
var Buffer = require('safe-buffer').Buffer
55
var express = require('..');
6+
var path = require('path')
67
var request = require('supertest');
78
var utils = require('./support/utils')
89

10+
var FIXTURES_PATH = path.join(__dirname, 'fixtures')
11+
912
describe('res', function(){
1013
describe('.download(path)', function(){
1114
it('should transfer as an attachment', function(done){
@@ -178,6 +181,77 @@ describe('res', function(){
178181
.end(done)
179182
})
180183
})
184+
185+
describe('with "root" option', function () {
186+
it('should allow relative path', function (done) {
187+
var app = express()
188+
189+
app.use(function (req, res) {
190+
res.download('name.txt', 'document', {
191+
root: FIXTURES_PATH
192+
})
193+
})
194+
195+
request(app)
196+
.get('/')
197+
.expect(200)
198+
.expect('Content-Disposition', 'attachment; filename="document"')
199+
.expect(utils.shouldHaveBody(Buffer.from('tobi')))
200+
.end(done)
201+
})
202+
203+
it('should allow up within root', function (done) {
204+
var app = express()
205+
206+
app.use(function (req, res) {
207+
res.download('fake/../name.txt', 'document', {
208+
root: FIXTURES_PATH
209+
})
210+
})
211+
212+
request(app)
213+
.get('/')
214+
.expect(200)
215+
.expect('Content-Disposition', 'attachment; filename="document"')
216+
.expect(utils.shouldHaveBody(Buffer.from('tobi')))
217+
.end(done)
218+
})
219+
220+
it('should reject up outside root', function (done) {
221+
var app = express()
222+
223+
app.use(function (req, res) {
224+
var p = '..' + path.sep +
225+
path.relative(path.dirname(FIXTURES_PATH), path.join(FIXTURES_PATH, 'name.txt'))
226+
227+
res.download(p, 'document', {
228+
root: FIXTURES_PATH
229+
})
230+
})
231+
232+
request(app)
233+
.get('/')
234+
.expect(403)
235+
.expect(utils.shouldNotHaveHeader('Content-Disposition'))
236+
.end(done)
237+
})
238+
239+
it('should reject reading outside root', function (done) {
240+
var app = express()
241+
242+
app.use(function (req, res) {
243+
res.download('../name.txt', 'document', {
244+
root: FIXTURES_PATH
245+
})
246+
})
247+
248+
request(app)
249+
.get('/')
250+
.expect(403)
251+
.expect(utils.shouldNotHaveHeader('Content-Disposition'))
252+
.end(done)
253+
})
254+
})
181255
})
182256

183257
describe('on failure', function(){

0 commit comments

Comments
 (0)