3 var after = require('after');
4 var assert = require('assert')
5 var AsyncLocalStorage = require('async_hooks').AsyncLocalStorage
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 describe('res', function(){
15 describe('.download(path)', function(){
16 it('should transfer as an attachment', function(done){
19 app.use(function(req, res){
20 res.download('test/fixtures/user.html');
25 .expect('Content-Type', 'text/html; charset=utf-8')
26 .expect('Content-Disposition', 'attachment; filename="user.html"')
27 .expect(200, '<p>{{user.name}}</p>', done)
30 it('should accept range requests', function (done) {
33 app.get('/', function (req, res) {
34 res.download('test/fixtures/user.html')
39 .expect('Accept-Ranges', 'bytes')
40 .expect(200, '<p>{{user.name}}</p>', done)
43 it('should respond with requested byte range', function (done) {
46 app.get('/', function (req, res) {
47 res.download('test/fixtures/user.html')
52 .set('Range', 'bytes=0-2')
53 .expect('Content-Range', 'bytes 0-2/20')
54 .expect(206, '<p>', done)
58 describe('.download(path, filename)', function(){
59 it('should provide an alternate filename', function(done){
62 app.use(function(req, res){
63 res.download('test/fixtures/user.html', 'document');
68 .expect('Content-Type', 'text/html; charset=utf-8')
69 .expect('Content-Disposition', 'attachment; filename="document"')
74 describe('.download(path, fn)', function(){
75 it('should invoke the callback', function(done){
77 var cb = after(2, done);
79 app.use(function(req, res){
80 res.download('test/fixtures/user.html', cb);
85 .expect('Content-Type', 'text/html; charset=utf-8')
86 .expect('Content-Disposition', 'attachment; filename="user.html"')
90 describe('async local storage', function () {
91 it('should presist store', function (done) {
93 var cb = after(2, done)
94 var store = { foo: 'bar' }
96 app.use(function (req, res, next) {
97 req.asyncLocalStorage = new AsyncLocalStorage()
98 req.asyncLocalStorage.run(store, next)
101 app.use(function (req, res) {
102 res.download('test/fixtures/name.txt', function (err) {
103 if (err) return cb(err)
105 var local = req.asyncLocalStorage.getStore()
107 assert.strictEqual(local.foo, 'bar')
114 .expect('Content-Type', 'text/plain; charset=utf-8')
115 .expect('Content-Disposition', 'attachment; filename="name.txt"')
116 .expect(200, 'tobi', cb)
119 it('should presist store on error', function (done) {
121 var store = { foo: 'bar' }
123 app.use(function (req, res, next) {
124 req.asyncLocalStorage = new AsyncLocalStorage()
125 req.asyncLocalStorage.run(store, next)
128 app.use(function (req, res) {
129 res.download('test/fixtures/does-not-exist', function (err) {
130 var local = req.asyncLocalStorage.getStore()
133 res.setHeader('x-store-foo', String(local.foo))
136 res.send(err ? 'got ' + err.status + ' error' : 'no error')
143 .expect('x-store-foo', 'bar')
144 .expect('got 404 error')
150 describe('.download(path, options)', function () {
151 it('should allow options to res.sendFile()', function (done) {
154 app.use(function (req, res) {
155 res.download('test/fixtures/.name', {
164 .expect('Content-Disposition', 'attachment; filename=".name"')
165 .expect('Cache-Control', 'public, max-age=14400')
166 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
170 describe('with "headers" option', function () {
171 it('should set headers on response', function (done) {
174 app.use(function (req, res) {
175 res.download('test/fixtures/user.html', {
186 .expect('X-Foo', 'Bar')
187 .expect('X-Bar', 'Foo')
191 it('should use last header when duplicated', function (done) {
194 app.use(function (req, res) {
195 res.download('test/fixtures/user.html', {
206 .expect('X-Foo', 'bar')
210 it('should override Content-Type', function (done) {
213 app.use(function (req, res) {
214 res.download('test/fixtures/user.html', {
216 'Content-Type': 'text/x-custom'
224 .expect('Content-Type', 'text/x-custom')
228 it('should not set headers on 404', function (done) {
231 app.use(function (req, res) {
232 res.download('test/fixtures/does-not-exist', {
242 .expect(utils.shouldNotHaveHeader('X-Foo'))
246 describe('when headers contains Content-Disposition', function () {
247 it('should be ignored', function (done) {
250 app.use(function (req, res) {
251 res.download('test/fixtures/user.html', {
253 'Content-Disposition': 'inline'
261 .expect('Content-Disposition', 'attachment; filename="user.html"')
265 it('should be ignored case-insensitively', function (done) {
268 app.use(function (req, res) {
269 res.download('test/fixtures/user.html', {
271 'content-disposition': 'inline'
279 .expect('Content-Disposition', 'attachment; filename="user.html"')
285 describe('with "root" option', function () {
286 it('should allow relative path', function (done) {
289 app.use(function (req, res) {
290 res.download('name.txt', {
298 .expect('Content-Disposition', 'attachment; filename="name.txt"')
299 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
303 it('should allow up within root', function (done) {
306 app.use(function (req, res) {
307 res.download('fake/../name.txt', {
315 .expect('Content-Disposition', 'attachment; filename="name.txt"')
316 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
320 it('should reject up outside root', function (done) {
323 app.use(function (req, res) {
324 var p = '..' + path.sep +
325 path.relative(path.dirname(FIXTURES_PATH), path.join(FIXTURES_PATH, 'name.txt'))
335 .expect(utils.shouldNotHaveHeader('Content-Disposition'))
339 it('should reject reading outside root', function (done) {
342 app.use(function (req, res) {
343 res.download('../name.txt', {
351 .expect(utils.shouldNotHaveHeader('Content-Disposition'))
357 describe('.download(path, filename, fn)', function(){
358 it('should invoke the callback', function(done){
360 var cb = after(2, done);
362 app.use(function(req, res){
363 res.download('test/fixtures/user.html', 'document', cb)
368 .expect('Content-Type', 'text/html; charset=utf-8')
369 .expect('Content-Disposition', 'attachment; filename="document"')
374 describe('.download(path, filename, options, fn)', function () {
375 it('should invoke the callback', function (done) {
377 var cb = after(2, done)
380 app.use(function (req, res) {
381 res.download('test/fixtures/user.html', 'document', options, cb)
387 .expect('Content-Type', 'text/html; charset=utf-8')
388 .expect('Content-Disposition', 'attachment; filename="document"')
392 it('should allow options to res.sendFile()', function (done) {
395 app.use(function (req, res) {
396 res.download('test/fixtures/.name', 'document', {
405 .expect('Content-Disposition', 'attachment; filename="document"')
406 .expect('Cache-Control', 'public, max-age=14400')
407 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
411 describe('when options.headers contains Content-Disposition', function () {
412 it('should be ignored', function (done) {
415 app.use(function (req, res) {
416 res.download('test/fixtures/user.html', 'document', {
418 'Content-Type': 'text/x-custom',
419 'Content-Disposition': 'inline'
427 .expect('Content-Type', 'text/x-custom')
428 .expect('Content-Disposition', 'attachment; filename="document"')
432 it('should be ignored case-insensitively', function (done) {
435 app.use(function (req, res) {
436 res.download('test/fixtures/user.html', 'document', {
438 'content-type': 'text/x-custom',
439 'content-disposition': 'inline'
447 .expect('Content-Type', 'text/x-custom')
448 .expect('Content-Disposition', 'attachment; filename="document"')
454 describe('on failure', function(){
455 it('should invoke the callback', function(done){
458 app.use(function (req, res, next) {
459 res.download('test/fixtures/foobar.html', function(err){
460 if (!err) return next(new Error('expected error'));
461 res.send('got ' + err.status + ' ' + err.code);
467 .expect(200, 'got 404 ENOENT', done);
470 it('should remove Content-Disposition', function(done){
473 app.use(function (req, res, next) {
474 res.download('test/fixtures/foobar.html', function(err){
475 if (!err) return next(new Error('expected error'));
482 .expect(utils.shouldNotHaveHeader('Content-Disposition'))
483 .expect(200, 'failed', done)