docs: update homepage link http to https (#5920)
[express.git] / test / res.download.js
blobf7d795d57c002016e9aaea04fe86cb1d42b8a8ca
1 'use strict'
3 var after = require('after');
4 var assert = require('assert')
5 var asyncHooks = tryRequire('async_hooks')
6 var Buffer = require('safe-buffer').Buffer
7 var express = require('..');
8 var path = require('path')
9 var request = require('supertest');
10 var utils = require('./support/utils')
12 var FIXTURES_PATH = path.join(__dirname, 'fixtures')
14 var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
15 ? describe
16 : describe.skip
18 describe('res', function(){
19 describe('.download(path)', function(){
20 it('should transfer as an attachment', function(done){
21 var app = express();
23 app.use(function(req, res){
24 res.download('test/fixtures/user.html');
25 });
27 request(app)
28 .get('/')
29 .expect('Content-Type', 'text/html; charset=utf-8')
30 .expect('Content-Disposition', 'attachment; filename="user.html"')
31 .expect(200, '<p>{{user.name}}</p>', done)
34 it('should accept range requests', function (done) {
35 var app = express()
37 app.get('/', function (req, res) {
38 res.download('test/fixtures/user.html')
41 request(app)
42 .get('/')
43 .expect('Accept-Ranges', 'bytes')
44 .expect(200, '<p>{{user.name}}</p>', done)
47 it('should respond with requested byte range', function (done) {
48 var app = express()
50 app.get('/', function (req, res) {
51 res.download('test/fixtures/user.html')
54 request(app)
55 .get('/')
56 .set('Range', 'bytes=0-2')
57 .expect('Content-Range', 'bytes 0-2/20')
58 .expect(206, '<p>', done)
62 describe('.download(path, filename)', function(){
63 it('should provide an alternate filename', function(done){
64 var app = express();
66 app.use(function(req, res){
67 res.download('test/fixtures/user.html', 'document');
68 });
70 request(app)
71 .get('/')
72 .expect('Content-Type', 'text/html; charset=utf-8')
73 .expect('Content-Disposition', 'attachment; filename="document"')
74 .expect(200, done)
78 describe('.download(path, fn)', function(){
79 it('should invoke the callback', function(done){
80 var app = express();
81 var cb = after(2, done);
83 app.use(function(req, res){
84 res.download('test/fixtures/user.html', cb);
85 });
87 request(app)
88 .get('/')
89 .expect('Content-Type', 'text/html; charset=utf-8')
90 .expect('Content-Disposition', 'attachment; filename="user.html"')
91 .expect(200, cb);
94 describeAsyncHooks('async local storage', function () {
95 it('should presist store', function (done) {
96 var app = express()
97 var cb = after(2, done)
98 var store = { foo: 'bar' }
100 app.use(function (req, res, next) {
101 req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
102 req.asyncLocalStorage.run(store, next)
105 app.use(function (req, res) {
106 res.download('test/fixtures/name.txt', function (err) {
107 if (err) return cb(err)
109 var local = req.asyncLocalStorage.getStore()
111 assert.strictEqual(local.foo, 'bar')
112 cb()
116 request(app)
117 .get('/')
118 .expect('Content-Type', 'text/plain; charset=utf-8')
119 .expect('Content-Disposition', 'attachment; filename="name.txt"')
120 .expect(200, 'tobi', cb)
123 it('should presist store on error', function (done) {
124 var app = express()
125 var store = { foo: 'bar' }
127 app.use(function (req, res, next) {
128 req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
129 req.asyncLocalStorage.run(store, next)
132 app.use(function (req, res) {
133 res.download('test/fixtures/does-not-exist', function (err) {
134 var local = req.asyncLocalStorage.getStore()
136 if (local) {
137 res.setHeader('x-store-foo', String(local.foo))
140 res.send(err ? 'got ' + err.status + ' error' : 'no error')
144 request(app)
145 .get('/')
146 .expect(200)
147 .expect('x-store-foo', 'bar')
148 .expect('got 404 error')
149 .end(done)
154 describe('.download(path, options)', function () {
155 it('should allow options to res.sendFile()', function (done) {
156 var app = express()
158 app.use(function (req, res) {
159 res.download('test/fixtures/.name', {
160 dotfiles: 'allow',
161 maxAge: '4h'
165 request(app)
166 .get('/')
167 .expect(200)
168 .expect('Content-Disposition', 'attachment; filename=".name"')
169 .expect('Cache-Control', 'public, max-age=14400')
170 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
171 .end(done)
174 describe('with "headers" option', function () {
175 it('should set headers on response', function (done) {
176 var app = express()
178 app.use(function (req, res) {
179 res.download('test/fixtures/user.html', {
180 headers: {
181 'X-Foo': 'Bar',
182 'X-Bar': 'Foo'
187 request(app)
188 .get('/')
189 .expect(200)
190 .expect('X-Foo', 'Bar')
191 .expect('X-Bar', 'Foo')
192 .end(done)
195 it('should use last header when duplicated', function (done) {
196 var app = express()
198 app.use(function (req, res) {
199 res.download('test/fixtures/user.html', {
200 headers: {
201 'X-Foo': 'Bar',
202 'x-foo': 'bar'
207 request(app)
208 .get('/')
209 .expect(200)
210 .expect('X-Foo', 'bar')
211 .end(done)
214 it('should override Content-Type', function (done) {
215 var app = express()
217 app.use(function (req, res) {
218 res.download('test/fixtures/user.html', {
219 headers: {
220 'Content-Type': 'text/x-custom'
225 request(app)
226 .get('/')
227 .expect(200)
228 .expect('Content-Type', 'text/x-custom')
229 .end(done)
232 it('should not set headers on 404', function (done) {
233 var app = express()
235 app.use(function (req, res) {
236 res.download('test/fixtures/does-not-exist', {
237 headers: {
238 'X-Foo': 'Bar'
243 request(app)
244 .get('/')
245 .expect(404)
246 .expect(utils.shouldNotHaveHeader('X-Foo'))
247 .end(done)
250 describe('when headers contains Content-Disposition', function () {
251 it('should be ignored', function (done) {
252 var app = express()
254 app.use(function (req, res) {
255 res.download('test/fixtures/user.html', {
256 headers: {
257 'Content-Disposition': 'inline'
262 request(app)
263 .get('/')
264 .expect(200)
265 .expect('Content-Disposition', 'attachment; filename="user.html"')
266 .end(done)
269 it('should be ignored case-insensitively', function (done) {
270 var app = express()
272 app.use(function (req, res) {
273 res.download('test/fixtures/user.html', {
274 headers: {
275 'content-disposition': 'inline'
280 request(app)
281 .get('/')
282 .expect(200)
283 .expect('Content-Disposition', 'attachment; filename="user.html"')
284 .end(done)
289 describe('with "root" option', function () {
290 it('should allow relative path', function (done) {
291 var app = express()
293 app.use(function (req, res) {
294 res.download('name.txt', {
295 root: FIXTURES_PATH
299 request(app)
300 .get('/')
301 .expect(200)
302 .expect('Content-Disposition', 'attachment; filename="name.txt"')
303 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
304 .end(done)
307 it('should allow up within root', function (done) {
308 var app = express()
310 app.use(function (req, res) {
311 res.download('fake/../name.txt', {
312 root: FIXTURES_PATH
316 request(app)
317 .get('/')
318 .expect(200)
319 .expect('Content-Disposition', 'attachment; filename="name.txt"')
320 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
321 .end(done)
324 it('should reject up outside root', function (done) {
325 var app = express()
327 app.use(function (req, res) {
328 var p = '..' + path.sep +
329 path.relative(path.dirname(FIXTURES_PATH), path.join(FIXTURES_PATH, 'name.txt'))
331 res.download(p, {
332 root: FIXTURES_PATH
336 request(app)
337 .get('/')
338 .expect(403)
339 .expect(utils.shouldNotHaveHeader('Content-Disposition'))
340 .end(done)
343 it('should reject reading outside root', function (done) {
344 var app = express()
346 app.use(function (req, res) {
347 res.download('../name.txt', {
348 root: FIXTURES_PATH
352 request(app)
353 .get('/')
354 .expect(403)
355 .expect(utils.shouldNotHaveHeader('Content-Disposition'))
356 .end(done)
361 describe('.download(path, filename, fn)', function(){
362 it('should invoke the callback', function(done){
363 var app = express();
364 var cb = after(2, done);
366 app.use(function(req, res){
367 res.download('test/fixtures/user.html', 'document', cb)
370 request(app)
371 .get('/')
372 .expect('Content-Type', 'text/html; charset=utf-8')
373 .expect('Content-Disposition', 'attachment; filename="document"')
374 .expect(200, cb);
378 describe('.download(path, filename, options, fn)', function () {
379 it('should invoke the callback', function (done) {
380 var app = express()
381 var cb = after(2, done)
382 var options = {}
384 app.use(function (req, res) {
385 res.download('test/fixtures/user.html', 'document', options, cb)
388 request(app)
389 .get('/')
390 .expect(200)
391 .expect('Content-Type', 'text/html; charset=utf-8')
392 .expect('Content-Disposition', 'attachment; filename="document"')
393 .end(cb)
396 it('should allow options to res.sendFile()', function (done) {
397 var app = express()
399 app.use(function (req, res) {
400 res.download('test/fixtures/.name', 'document', {
401 dotfiles: 'allow',
402 maxAge: '4h'
406 request(app)
407 .get('/')
408 .expect(200)
409 .expect('Content-Disposition', 'attachment; filename="document"')
410 .expect('Cache-Control', 'public, max-age=14400')
411 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
412 .end(done)
415 describe('when options.headers contains Content-Disposition', function () {
416 it('should be ignored', function (done) {
417 var app = express()
419 app.use(function (req, res) {
420 res.download('test/fixtures/user.html', 'document', {
421 headers: {
422 'Content-Type': 'text/x-custom',
423 'Content-Disposition': 'inline'
428 request(app)
429 .get('/')
430 .expect(200)
431 .expect('Content-Type', 'text/x-custom')
432 .expect('Content-Disposition', 'attachment; filename="document"')
433 .end(done)
436 it('should be ignored case-insensitively', function (done) {
437 var app = express()
439 app.use(function (req, res) {
440 res.download('test/fixtures/user.html', 'document', {
441 headers: {
442 'content-type': 'text/x-custom',
443 'content-disposition': 'inline'
448 request(app)
449 .get('/')
450 .expect(200)
451 .expect('Content-Type', 'text/x-custom')
452 .expect('Content-Disposition', 'attachment; filename="document"')
453 .end(done)
458 describe('on failure', function(){
459 it('should invoke the callback', function(done){
460 var app = express();
462 app.use(function (req, res, next) {
463 res.download('test/fixtures/foobar.html', function(err){
464 if (!err) return next(new Error('expected error'));
465 res.send('got ' + err.status + ' ' + err.code);
469 request(app)
470 .get('/')
471 .expect(200, 'got 404 ENOENT', done);
474 it('should remove Content-Disposition', function(done){
475 var app = express()
477 app.use(function (req, res, next) {
478 res.download('test/fixtures/foobar.html', function(err){
479 if (!err) return next(new Error('expected error'));
480 res.end('failed');
484 request(app)
485 .get('/')
486 .expect(utils.shouldNotHaveHeader('Content-Disposition'))
487 .expect(200, 'failed', done)
492 function tryRequire (name) {
493 try {
494 return require(name)
495 } catch (e) {
496 return {}