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) {