cleanup: remove unnecessary require for global Buffer
[express.git] / test / res.sendFile.js
blobe211fe9cb6792a9be7b79cef82aacbe22d4a1da5
1 'use strict'
3 var after = require('after');
4 var assert = require('assert')
5 var asyncHooks = tryRequire('async_hooks')
6 var express = require('../')
7 , request = require('supertest')
8 var onFinished = require('on-finished');
9 var path = require('path');
10 var fixtures = path.join(__dirname, 'fixtures');
11 var utils = require('./support/utils');
13 var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
14 ? describe
15 : describe.skip
17 describe('res', function(){
18 describe('.sendFile(path)', function () {
19 it('should error missing path', function (done) {
20 var app = createApp();
22 request(app)
23 .get('/')
24 .expect(500, /path.*required/, done);
25 });
27 it('should error for non-string path', function (done) {
28 var app = createApp(42)
30 request(app)
31 .get('/')
32 .expect(500, /TypeError: path must be a string to res.sendFile/, done)
35 it('should error for non-absolute path', function (done) {
36 var app = createApp('name.txt')
38 request(app)
39 .get('/')
40 .expect(500, /TypeError: path must be absolute/, done)
43 it('should transfer a file', function (done) {
44 var app = createApp(path.resolve(fixtures, 'name.txt'));
46 request(app)
47 .get('/')
48 .expect(200, 'tobi', done);
49 });
51 it('should transfer a file with special characters in string', function (done) {
52 var app = createApp(path.resolve(fixtures, '% of dogs.txt'));
54 request(app)
55 .get('/')
56 .expect(200, '20%', done);
57 });
59 it('should include ETag', function (done) {
60 var app = createApp(path.resolve(fixtures, 'name.txt'));
62 request(app)
63 .get('/')
64 .expect('ETag', /^(?:W\/)?"[^"]+"$/)
65 .expect(200, 'tobi', done);
66 });
68 it('should 304 when ETag matches', function (done) {
69 var app = createApp(path.resolve(fixtures, 'name.txt'));
71 request(app)
72 .get('/')
73 .expect('ETag', /^(?:W\/)?"[^"]+"$/)
74 .expect(200, 'tobi', function (err, res) {
75 if (err) return done(err);
76 var etag = res.headers.etag;
77 request(app)
78 .get('/')
79 .set('If-None-Match', etag)
80 .expect(304, done);
81 });
82 });
84 it('should 404 for directory', function (done) {
85 var app = createApp(path.resolve(fixtures, 'blog'));
87 request(app)
88 .get('/')
89 .expect(404, done);
90 });
92 it('should 404 when not found', function (done) {
93 var app = createApp(path.resolve(fixtures, 'does-no-exist'));
95 app.use(function (req, res) {
96 res.statusCode = 200;
97 res.send('no!');
98 });
100 request(app)
101 .get('/')
102 .expect(404, done);
105 it('should send cache-control by default', function (done) {
106 var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'))
108 request(app)
109 .get('/')
110 .expect('Cache-Control', 'public, max-age=0')
111 .expect(200, done)
114 it('should not serve dotfiles by default', function (done) {
115 var app = createApp(path.resolve(__dirname, 'fixtures/.name'))
117 request(app)
118 .get('/')
119 .expect(404, done)
122 it('should not override manual content-types', function (done) {
123 var app = express();
125 app.use(function (req, res) {
126 res.contentType('application/x-bogus');
127 res.sendFile(path.resolve(fixtures, 'name.txt'));
130 request(app)
131 .get('/')
132 .expect('Content-Type', 'application/x-bogus')
133 .end(done);
136 it('should not error if the client aborts', function (done) {
137 var app = express();
138 var cb = after(2, done)
139 var error = null
141 app.use(function (req, res) {
142 setImmediate(function () {
143 res.sendFile(path.resolve(fixtures, 'name.txt'));
144 setTimeout(function () {
145 cb(error)
146 }, 10)
148 test.req.abort()
151 app.use(function (err, req, res, next) {
152 error = err
153 next(err)
156 var server = app.listen()
157 var test = request(server).get('/')
158 test.end(function (err) {
159 assert.ok(err)
160 server.close(cb)
165 describe('.sendFile(path, fn)', function () {
166 it('should invoke the callback when complete', function (done) {
167 var cb = after(2, done);
168 var app = createApp(path.resolve(fixtures, 'name.txt'), cb);
170 request(app)
171 .get('/')
172 .expect(200, cb);
175 it('should invoke the callback when client aborts', function (done) {
176 var cb = after(2, done)
177 var app = express();
179 app.use(function (req, res) {
180 setImmediate(function () {
181 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
182 assert.ok(err)
183 assert.strictEqual(err.code, 'ECONNABORTED')
184 cb()
187 test.req.abort()
190 var server = app.listen()
191 var test = request(server).get('/')
192 test.end(function (err) {
193 assert.ok(err)
194 server.close(cb)
198 it('should invoke the callback when client already aborted', function (done) {
199 var cb = after(2, done)
200 var app = express();
202 app.use(function (req, res) {
203 onFinished(res, function () {
204 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
205 assert.ok(err)
206 assert.strictEqual(err.code, 'ECONNABORTED')
207 cb()
210 test.req.abort()
213 var server = app.listen()
214 var test = request(server).get('/')
215 test.end(function (err) {
216 assert.ok(err)
217 server.close(cb)
221 it('should invoke the callback without error when HEAD', function (done) {
222 var app = express();
223 var cb = after(2, done);
225 app.use(function (req, res) {
226 res.sendFile(path.resolve(fixtures, 'name.txt'), cb);
229 request(app)
230 .head('/')
231 .expect(200, cb);
234 it('should invoke the callback without error when 304', function (done) {
235 var app = express();
236 var cb = after(3, done);
238 app.use(function (req, res) {
239 res.sendFile(path.resolve(fixtures, 'name.txt'), cb);
242 request(app)
243 .get('/')
244 .expect('ETag', /^(?:W\/)?"[^"]+"$/)
245 .expect(200, 'tobi', function (err, res) {
246 if (err) return cb(err);
247 var etag = res.headers.etag;
248 request(app)
249 .get('/')
250 .set('If-None-Match', etag)
251 .expect(304, cb);
255 it('should invoke the callback on 404', function(done){
256 var app = express();
258 app.use(function (req, res) {
259 res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {
260 res.send(err ? 'got ' + err.status + ' error' : 'no error')
264 request(app)
265 .get('/')
266 .expect(200, 'got 404 error', done)
269 describeAsyncHooks('async local storage', function () {
270 it('should presist store', function (done) {
271 var app = express()
272 var cb = after(2, done)
273 var store = { foo: 'bar' }
275 app.use(function (req, res, next) {
276 req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
277 req.asyncLocalStorage.run(store, next)
280 app.use(function (req, res) {
281 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
282 if (err) return cb(err)
284 var local = req.asyncLocalStorage.getStore()
286 assert.strictEqual(local.foo, 'bar')
287 cb()
291 request(app)
292 .get('/')
293 .expect('Content-Type', 'text/plain; charset=utf-8')
294 .expect(200, 'tobi', cb)
297 it('should presist store on error', function (done) {
298 var app = express()
299 var store = { foo: 'bar' }
301 app.use(function (req, res, next) {
302 req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
303 req.asyncLocalStorage.run(store, next)
306 app.use(function (req, res) {
307 res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {
308 var local = req.asyncLocalStorage.getStore()
310 if (local) {
311 res.setHeader('x-store-foo', String(local.foo))
314 res.send(err ? 'got ' + err.status + ' error' : 'no error')
318 request(app)
319 .get('/')
320 .expect(200)
321 .expect('x-store-foo', 'bar')
322 .expect('got 404 error')
323 .end(done)
328 describe('.sendFile(path, options)', function () {
329 it('should pass options to send module', function (done) {
330 request(createApp(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 }))
331 .get('/')
332 .expect(200, 'to', done)
335 describe('with "acceptRanges" option', function () {
336 describe('when true', function () {
337 it('should advertise byte range accepted', function (done) {
338 var app = express()
340 app.use(function (req, res) {
341 res.sendFile(path.resolve(fixtures, 'nums.txt'), {
342 acceptRanges: true
346 request(app)
347 .get('/')
348 .expect(200)
349 .expect('Accept-Ranges', 'bytes')
350 .expect('123456789')
351 .end(done)
354 it('should respond to range request', function (done) {
355 var app = express()
357 app.use(function (req, res) {
358 res.sendFile(path.resolve(fixtures, 'nums.txt'), {
359 acceptRanges: true
363 request(app)
364 .get('/')
365 .set('Range', 'bytes=0-4')
366 .expect(206, '12345', done)
370 describe('when false', function () {
371 it('should not advertise accept-ranges', function (done) {
372 var app = express()
374 app.use(function (req, res) {
375 res.sendFile(path.resolve(fixtures, 'nums.txt'), {
376 acceptRanges: false
380 request(app)
381 .get('/')
382 .expect(200)
383 .expect(utils.shouldNotHaveHeader('Accept-Ranges'))
384 .end(done)
387 it('should not honor range requests', function (done) {
388 var app = express()
390 app.use(function (req, res) {
391 res.sendFile(path.resolve(fixtures, 'nums.txt'), {
392 acceptRanges: false
396 request(app)
397 .get('/')
398 .set('Range', 'bytes=0-4')
399 .expect(200, '123456789', done)
404 describe('with "cacheControl" option', function () {
405 describe('when true', function () {
406 it('should send cache-control header', function (done) {
407 var app = express()
409 app.use(function (req, res) {
410 res.sendFile(path.resolve(fixtures, 'user.html'), {
411 cacheControl: true
415 request(app)
416 .get('/')
417 .expect(200)
418 .expect('Cache-Control', 'public, max-age=0')
419 .end(done)
423 describe('when false', function () {
424 it('should not send cache-control header', function (done) {
425 var app = express()
427 app.use(function (req, res) {
428 res.sendFile(path.resolve(fixtures, 'user.html'), {
429 cacheControl: false
433 request(app)
434 .get('/')
435 .expect(200)
436 .expect(utils.shouldNotHaveHeader('Cache-Control'))
437 .end(done)
442 describe('with "dotfiles" option', function () {
443 describe('when "allow"', function () {
444 it('should allow dotfiles', function (done) {
445 var app = express()
447 app.use(function (req, res) {
448 res.sendFile(path.resolve(fixtures, '.name'), {
449 dotfiles: 'allow'
453 request(app)
454 .get('/')
455 .expect(200)
456 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
457 .end(done)
461 describe('when "deny"', function () {
462 it('should deny dotfiles', function (done) {
463 var app = express()
465 app.use(function (req, res) {
466 res.sendFile(path.resolve(fixtures, '.name'), {
467 dotfiles: 'deny'
471 request(app)
472 .get('/')
473 .expect(403)
474 .expect(/Forbidden/)
475 .end(done)
479 describe('when "ignore"', function () {
480 it('should ignore dotfiles', function (done) {
481 var app = express()
483 app.use(function (req, res) {
484 res.sendFile(path.resolve(fixtures, '.name'), {
485 dotfiles: 'ignore'
489 request(app)
490 .get('/')
491 .expect(404)
492 .expect(/Not Found/)
493 .end(done)
498 describe('with "headers" option', function () {
499 it('should set headers on response', function (done) {
500 var app = express()
502 app.use(function (req, res) {
503 res.sendFile(path.resolve(fixtures, 'user.html'), {
504 headers: {
505 'X-Foo': 'Bar',
506 'X-Bar': 'Foo'
511 request(app)
512 .get('/')
513 .expect(200)
514 .expect('X-Foo', 'Bar')
515 .expect('X-Bar', 'Foo')
516 .end(done)
519 it('should use last header when duplicated', function (done) {
520 var app = express()
522 app.use(function (req, res) {
523 res.sendFile(path.resolve(fixtures, 'user.html'), {
524 headers: {
525 'X-Foo': 'Bar',
526 'x-foo': 'bar'
531 request(app)
532 .get('/')
533 .expect(200)
534 .expect('X-Foo', 'bar')
535 .end(done)
538 it('should override Content-Type', function (done) {
539 var app = express()
541 app.use(function (req, res) {
542 res.sendFile(path.resolve(fixtures, 'user.html'), {
543 headers: {
544 'Content-Type': 'text/x-custom'
549 request(app)
550 .get('/')
551 .expect(200)
552 .expect('Content-Type', 'text/x-custom')
553 .end(done)
556 it('should not set headers on 404', function (done) {
557 var app = express()
559 app.use(function (req, res) {
560 res.sendFile(path.resolve(fixtures, 'does-not-exist'), {
561 headers: {
562 'X-Foo': 'Bar'
567 request(app)
568 .get('/')
569 .expect(404)
570 .expect(utils.shouldNotHaveHeader('X-Foo'))
571 .end(done)
575 describe('with "immutable" option', function () {
576 describe('when true', function () {
577 it('should send cache-control header with immutable', function (done) {
578 var app = express()
580 app.use(function (req, res) {
581 res.sendFile(path.resolve(fixtures, 'user.html'), {
582 immutable: true
586 request(app)
587 .get('/')
588 .expect(200)
589 .expect('Cache-Control', 'public, max-age=0, immutable')
590 .end(done)
594 describe('when false', function () {
595 it('should not send cache-control header with immutable', function (done) {
596 var app = express()
598 app.use(function (req, res) {
599 res.sendFile(path.resolve(fixtures, 'user.html'), {
600 immutable: false
604 request(app)
605 .get('/')
606 .expect(200)
607 .expect('Cache-Control', 'public, max-age=0')
608 .end(done)
613 describe('with "lastModified" option', function () {
614 describe('when true', function () {
615 it('should send last-modified header', function (done) {
616 var app = express()
618 app.use(function (req, res) {
619 res.sendFile(path.resolve(fixtures, 'user.html'), {
620 lastModified: true
624 request(app)
625 .get('/')
626 .expect(200)
627 .expect(utils.shouldHaveHeader('Last-Modified'))
628 .end(done)
631 it('should conditionally respond with if-modified-since', function (done) {
632 var app = express()
634 app.use(function (req, res) {
635 res.sendFile(path.resolve(fixtures, 'user.html'), {
636 lastModified: true
640 request(app)
641 .get('/')
642 .set('If-Modified-Since', (new Date(Date.now() + 99999).toUTCString()))
643 .expect(304, done)
647 describe('when false', function () {
648 it('should not have last-modified header', function (done) {
649 var app = express()
651 app.use(function (req, res) {
652 res.sendFile(path.resolve(fixtures, 'user.html'), {
653 lastModified: false
657 request(app)
658 .get('/')
659 .expect(200)
660 .expect(utils.shouldNotHaveHeader('Last-Modified'))
661 .end(done)
664 it('should not honor if-modified-since', function (done) {
665 var app = express()
667 app.use(function (req, res) {
668 res.sendFile(path.resolve(fixtures, 'user.html'), {
669 lastModified: false
673 request(app)
674 .get('/')
675 .set('If-Modified-Since', (new Date(Date.now() + 99999).toUTCString()))
676 .expect(200)
677 .expect(utils.shouldNotHaveHeader('Last-Modified'))
678 .end(done)
683 describe('with "maxAge" option', function () {
684 it('should set cache-control max-age to milliseconds', function (done) {
685 var app = express()
687 app.use(function (req, res) {
688 res.sendFile(path.resolve(fixtures, 'user.html'), {
689 maxAge: 20000
693 request(app)
694 .get('/')
695 .expect(200)
696 .expect('Cache-Control', 'public, max-age=20')
697 .end(done)
700 it('should cap cache-control max-age to 1 year', function (done) {
701 var app = express()
703 app.use(function (req, res) {
704 res.sendFile(path.resolve(fixtures, 'user.html'), {
705 maxAge: 99999999999
709 request(app)
710 .get('/')
711 .expect(200)
712 .expect('Cache-Control', 'public, max-age=31536000')
713 .end(done)
716 it('should min cache-control max-age to 0', function (done) {
717 var app = express()
719 app.use(function (req, res) {
720 res.sendFile(path.resolve(fixtures, 'user.html'), {
721 maxAge: -20000
725 request(app)
726 .get('/')
727 .expect(200)
728 .expect('Cache-Control', 'public, max-age=0')
729 .end(done)
732 it('should floor cache-control max-age', function (done) {
733 var app = express()
735 app.use(function (req, res) {
736 res.sendFile(path.resolve(fixtures, 'user.html'), {
737 maxAge: 21911.23
741 request(app)
742 .get('/')
743 .expect(200)
744 .expect('Cache-Control', 'public, max-age=21')
745 .end(done)
748 describe('when cacheControl: false', function () {
749 it('should not send cache-control', function (done) {
750 var app = express()
752 app.use(function (req, res) {
753 res.sendFile(path.resolve(fixtures, 'user.html'), {
754 cacheControl: false,
755 maxAge: 20000
759 request(app)
760 .get('/')
761 .expect(200)
762 .expect(utils.shouldNotHaveHeader('Cache-Control'))
763 .end(done)
767 describe('when string', function () {
768 it('should accept plain number as milliseconds', function (done) {
769 var app = express()
771 app.use(function (req, res) {
772 res.sendFile(path.resolve(fixtures, 'user.html'), {
773 maxAge: '20000'
777 request(app)
778 .get('/')
779 .expect(200)
780 .expect('Cache-Control', 'public, max-age=20')
781 .end(done)
784 it('should accept suffix "s" for seconds', function (done) {
785 var app = express()
787 app.use(function (req, res) {
788 res.sendFile(path.resolve(fixtures, 'user.html'), {
789 maxAge: '20s'
793 request(app)
794 .get('/')
795 .expect(200)
796 .expect('Cache-Control', 'public, max-age=20')
797 .end(done)
800 it('should accept suffix "m" for minutes', function (done) {
801 var app = express()
803 app.use(function (req, res) {
804 res.sendFile(path.resolve(fixtures, 'user.html'), {
805 maxAge: '20m'
809 request(app)
810 .get('/')
811 .expect(200)
812 .expect('Cache-Control', 'public, max-age=1200')
813 .end(done)
816 it('should accept suffix "d" for days', function (done) {
817 var app = express()
819 app.use(function (req, res) {
820 res.sendFile(path.resolve(fixtures, 'user.html'), {
821 maxAge: '20d'
825 request(app)
826 .get('/')
827 .expect(200)
828 .expect('Cache-Control', 'public, max-age=1728000')
829 .end(done)
834 describe('with "root" option', function () {
835 it('should allow relative path', function (done) {
836 var app = express()
838 app.use(function (req, res) {
839 res.sendFile('name.txt', {
840 root: fixtures
844 request(app)
845 .get('/')
846 .expect(200, 'tobi', done)
849 it('should allow up within root', function (done) {
850 var app = express()
852 app.use(function (req, res) {
853 res.sendFile('fake/../name.txt', {
854 root: fixtures
858 request(app)
859 .get('/')
860 .expect(200, 'tobi', done)
863 it('should reject up outside root', function (done) {
864 var app = express()
866 app.use(function (req, res) {
867 res.sendFile('..' + path.sep + path.relative(path.dirname(fixtures), path.join(fixtures, 'name.txt')), {
868 root: fixtures
872 request(app)
873 .get('/')
874 .expect(403, done)
877 it('should reject reading outside root', function (done) {
878 var app = express()
880 app.use(function (req, res) {
881 res.sendFile('../name.txt', {
882 root: fixtures
886 request(app)
887 .get('/')
888 .expect(403, done)
894 function createApp(path, options, fn) {
895 var app = express();
897 app.use(function (req, res) {
898 res.sendFile(path, options, fn);
901 return app;
904 function tryRequire (name) {
905 try {
906 return require(name)
907 } catch (e) {
908 return {}