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'
18 describe('res', function(){
19 describe('.download(path)', function(){
20 it('should transfer as an attachment', function(done
){
23 app
.use(function(req
, res
){
24 res
.download('test/fixtures/user.html');
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
) {
37 app
.get('/', function (req
, res
) {
38 res
.download('test/fixtures/user.html')
43 .expect('Accept-Ranges', 'bytes')
44 .expect(200, '<p>{{user.name}}</p>', done
)
47 it('should respond with requested byte range', function (done
) {
50 app
.get('/', function (req
, res
) {
51 res
.download('test/fixtures/user.html')
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
){
66 app
.use(function(req
, res
){
67 res
.download('test/fixtures/user.html', 'document');
72 .expect('Content-Type', 'text/html; charset=utf-8')
73 .expect('Content-Disposition', 'attachment; filename="document"')
78 describe('.download(path, fn)', function(){
79 it('should invoke the callback', function(done
){
81 var cb
= after(2, done
);
83 app
.use(function(req
, res
){
84 res
.download('test/fixtures/user.html', cb
);
89 .expect('Content-Type', 'text/html; charset=utf-8')
90 .expect('Content-Disposition', 'attachment; filename="user.html"')
94 describeAsyncHooks('async local storage', function () {
95 it('should presist store', function (done
) {
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')
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
) {
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()
137 res
.setHeader('x-store-foo', String(local
.foo
))
140 res
.send(err
? 'got ' + err
.status
+ ' error' : 'no error')
147 .expect('x-store-foo', 'bar')
148 .expect('got 404 error')
154 describe('.download(path, options)', function () {
155 it('should allow options to res.sendFile()', function (done
) {
158 app
.use(function (req
, res
) {
159 res
.download('test/fixtures/.name', {
168 .expect('Content-Disposition', 'attachment; filename=".name"')
169 .expect('Cache-Control', 'public, max-age=14400')
170 .expect(utils
.shouldHaveBody(Buffer
.from('tobi')))
174 describe('with "headers" option', function () {
175 it('should set headers on response', function (done
) {
178 app
.use(function (req
, res
) {
179 res
.download('test/fixtures/user.html', {
190 .expect('X-Foo', 'Bar')
191 .expect('X-Bar', 'Foo')
195 it('should use last header when duplicated', function (done
) {
198 app
.use(function (req
, res
) {
199 res
.download('test/fixtures/user.html', {
210 .expect('X-Foo', 'bar')
214 it('should override Content-Type', function (done
) {
217 app
.use(function (req
, res
) {
218 res
.download('test/fixtures/user.html', {
220 'Content-Type': 'text/x-custom'
228 .expect('Content-Type', 'text/x-custom')
232 it('should not set headers on 404', function (done
) {
235 app
.use(function (req
, res
) {
236 res
.download('test/fixtures/does-not-exist', {
246 .expect(utils
.shouldNotHaveHeader('X-Foo'))
250 describe('when headers contains Content-Disposition', function () {
251 it('should be ignored', function (done
) {
254 app
.use(function (req
, res
) {
255 res
.download('test/fixtures/user.html', {
257 'Content-Disposition': 'inline'
265 .expect('Content-Disposition', 'attachment; filename="user.html"')
269 it('should be ignored case-insensitively', function (done
) {
272 app
.use(function (req
, res
) {
273 res
.download('test/fixtures/user.html', {
275 'content-disposition': 'inline'
283 .expect('Content-Disposition', 'attachment; filename="user.html"')
289 describe('with "root" option', function () {
290 it('should allow relative path', function (done
) {
293 app
.use(function (req
, res
) {
294 res
.download('name.txt', {
302 .expect('Content-Disposition', 'attachment; filename="name.txt"')
303 .expect(utils
.shouldHaveBody(Buffer
.from('tobi')))
307 it('should allow up within root', function (done
) {
310 app
.use(function (req
, res
) {
311 res
.download('fake/../name.txt', {
319 .expect('Content-Disposition', 'attachment; filename="name.txt"')
320 .expect(utils
.shouldHaveBody(Buffer
.from('tobi')))
324 it('should reject up outside root', function (done
) {
327 app
.use(function (req
, res
) {
328 var p
= '..' + path
.sep
+
329 path
.relative(path
.dirname(FIXTURES_PATH
), path
.join(FIXTURES_PATH
, 'name.txt'))
339 .expect(utils
.shouldNotHaveHeader('Content-Disposition'))
343 it('should reject reading outside root', function (done
) {
346 app
.use(function (req
, res
) {
347 res
.download('../name.txt', {
355 .expect(utils
.shouldNotHaveHeader('Content-Disposition'))
361 describe('.download(path, filename, fn)', function(){
362 it('should invoke the callback', function(done
){
364 var cb
= after(2, done
);
366 app
.use(function(req
, res
){
367 res
.download('test/fixtures/user.html', 'document', cb
)
372 .expect('Content-Type', 'text/html; charset=utf-8')
373 .expect('Content-Disposition', 'attachment; filename="document"')
378 describe('.download(path, filename, options, fn)', function () {
379 it('should invoke the callback', function (done
) {
381 var cb
= after(2, done
)
384 app
.use(function (req
, res
) {
385 res
.download('test/fixtures/user.html', 'document', options
, cb
)
391 .expect('Content-Type', 'text/html; charset=utf-8')
392 .expect('Content-Disposition', 'attachment; filename="document"')
396 it('should allow options to res.sendFile()', function (done
) {
399 app
.use(function (req
, res
) {
400 res
.download('test/fixtures/.name', 'document', {
409 .expect('Content-Disposition', 'attachment; filename="document"')
410 .expect('Cache-Control', 'public, max-age=14400')
411 .expect(utils
.shouldHaveBody(Buffer
.from('tobi')))
415 describe('when options.headers contains Content-Disposition', function () {
416 it('should be ignored', function (done
) {
419 app
.use(function (req
, res
) {
420 res
.download('test/fixtures/user.html', 'document', {
422 'Content-Type': 'text/x-custom',
423 'Content-Disposition': 'inline'
431 .expect('Content-Type', 'text/x-custom')
432 .expect('Content-Disposition', 'attachment; filename="document"')
436 it('should be ignored case-insensitively', function (done
) {
439 app
.use(function (req
, res
) {
440 res
.download('test/fixtures/user.html', 'document', {
442 'content-type': 'text/x-custom',
443 'content-disposition': 'inline'
451 .expect('Content-Type', 'text/x-custom')
452 .expect('Content-Disposition', 'attachment; filename="document"')
458 describe('on failure', function(){
459 it('should invoke the callback', function(done
){
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
);
471 .expect(200, 'got 404 ENOENT', done
);
474 it('should remove Content-Disposition', function(done
){
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'));
486 .expect(utils
.shouldNotHaveHeader('Content-Disposition'))
487 .expect(200, 'failed', done
)
492 function tryRequire (name
) {