fix(buffer): use node:buffer instead of safe-buffer (#6071)
[express.git] / test / res.sendFile.js
blobd0b4b1b50cfb8d03fb747942331772ba542811ec
1 'use strict'
3 var after = require('after');
4 var assert = require('assert')
5 var asyncHooks = tryRequire('async_hooks')
6 var Buffer = require('node:buffer').Buffer
7 var express = require('../')
8 , request = require('supertest')
9 var onFinished = require('on-finished');
10 var path = require('path');
11 var fixtures = path.join(__dirname, 'fixtures');
12 var utils = require('./support/utils');
14 var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
15 ? describe
16 : describe.skip
18 describe('res', function(){
19 describe('.sendFile(path)', function () {
20 it('should error missing path', function (done) {
21 var app = createApp();
23 request(app)
24 .get('/')
25 .expect(500, /path.*required/, done);
26 });
28 it('should error for non-string path', function (done) {
29 var app = createApp(42)
31 request(app)
32 .get('/')
33 .expect(500, /TypeError: path must be a string to res.sendFile/, done)
36 it('should error for non-absolute path', function (done) {
37 var app = createApp('name.txt')
39 request(app)
40 .get('/')
41 .expect(500, /TypeError: path must be absolute/, done)
44 it('should transfer a file', function (done) {
45 var app = createApp(path.resolve(fixtures, 'name.txt'));
47 request(app)
48 .get('/')
49 .expect(200, 'tobi', done);
50 });
52 it('should transfer a file with special characters in string', function (done) {
53 var app = createApp(path.resolve(fixtures, '% of dogs.txt'));
55 request(app)
56 .get('/')
57 .expect(200, '20%', done);
58 });
60 it('should include ETag', function (done) {
61 var app = createApp(path.resolve(fixtures, 'name.txt'));
63 request(app)
64 .get('/')
65 .expect('ETag', /^(?:W\/)?"[^"]+"$/)
66 .expect(200, 'tobi', done);
67 });
69 it('should 304 when ETag matches', function (done) {
70 var app = createApp(path.resolve(fixtures, 'name.txt'));
72 request(app)
73 .get('/')
74 .expect('ETag', /^(?:W\/)?"[^"]+"$/)
75 .expect(200, 'tobi', function (err, res) {
76 if (err) return done(err);
77 var etag = res.headers.etag;
78 request(app)
79 .get('/')
80 .set('If-None-Match', etag)
81 .expect(304, done);
82 });
83 });
85 it('should 404 for directory', function (done) {
86 var app = createApp(path.resolve(fixtures, 'blog'));
88 request(app)
89 .get('/')
90 .expect(404, done);
91 });
93 it('should 404 when not found', function (done) {
94 var app = createApp(path.resolve(fixtures, 'does-no-exist'));
96 app.use(function (req, res) {
97 res.statusCode = 200;
98 res.send('no!');
99 });
101 request(app)
102 .get('/')
103 .expect(404, done);
106 it('should send cache-control by default', function (done) {
107 var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'))
109 request(app)
110 .get('/')
111 .expect('Cache-Control', 'public, max-age=0')
112 .expect(200, done)
115 it('should not serve dotfiles by default', function (done) {
116 var app = createApp(path.resolve(__dirname, 'fixtures/.name'))
118 request(app)
119 .get('/')
120 .expect(404, done)
123 it('should not override manual content-types', function (done) {
124 var app = express();
126 app.use(function (req, res) {
127 res.contentType('application/x-bogus');
128 res.sendFile(path.resolve(fixtures, 'name.txt'));
131 request(app)
132 .get('/')
133 .expect('Content-Type', 'application/x-bogus')
134 .end(done);
137 it('should not error if the client aborts', function (done) {
138 var app = express();
139 var cb = after(2, done)
140 var error = null
142 app.use(function (req, res) {
143 setImmediate(function () {
144 res.sendFile(path.resolve(fixtures, 'name.txt'));
145 setTimeout(function () {
146 cb(error)
147 }, 10)
149 test.req.abort()
152 app.use(function (err, req, res, next) {
153 error = err
154 next(err)
157 var server = app.listen()
158 var test = request(server).get('/')
159 test.end(function (err) {
160 assert.ok(err)
161 server.close(cb)
166 describe('.sendFile(path, fn)', function () {
167 it('should invoke the callback when complete', function (done) {
168 var cb = after(2, done);
169 var app = createApp(path.resolve(fixtures, 'name.txt'), cb);
171 request(app)
172 .get('/')
173 .expect(200, cb);
176 it('should invoke the callback when client aborts', function (done) {
177 var cb = after(2, done)
178 var app = express();
180 app.use(function (req, res) {
181 setImmediate(function () {
182 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
183 assert.ok(err)
184 assert.strictEqual(err.code, 'ECONNABORTED')
185 cb()
188 test.req.abort()
191 var server = app.listen()
192 var test = request(server).get('/')
193 test.end(function (err) {
194 assert.ok(err)
195 server.close(cb)
199 it('should invoke the callback when client already aborted', function (done) {
200 var cb = after(2, done)
201 var app = express();
203 app.use(function (req, res) {
204 onFinished(res, function () {
205 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
206 assert.ok(err)
207 assert.strictEqual(err.code, 'ECONNABORTED')
208 cb()
211 test.req.abort()
214 var server = app.listen()
215 var test = request(server).get('/')
216 test.end(function (err) {
217 assert.ok(err)
218 server.close(cb)
222 it('should invoke the callback without error when HEAD', function (done) {
223 var app = express();
224 var cb = after(2, done);
226 app.use(function (req, res) {
227 res.sendFile(path.resolve(fixtures, 'name.txt'), cb);
230 request(app)
231 .head('/')
232 .expect(200, cb);
235 it('should invoke the callback without error when 304', function (done) {
236 var app = express();
237 var cb = after(3, done);
239 app.use(function (req, res) {
240 res.sendFile(path.resolve(fixtures, 'name.txt'), cb);
243 request(app)
244 .get('/')
245 .expect('ETag', /^(?:W\/)?"[^"]+"$/)
246 .expect(200, 'tobi', function (err, res) {
247 if (err) return cb(err);
248 var etag = res.headers.etag;
249 request(app)
250 .get('/')
251 .set('If-None-Match', etag)
252 .expect(304, cb);
256 it('should invoke the callback on 404', function(done){
257 var app = express();
259 app.use(function (req, res) {
260 res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {
261 res.send(err ? 'got ' + err.status + ' error' : 'no error')
265 request(app)
266 .get('/')
267 .expect(200, 'got 404 error', done)
270 describeAsyncHooks('async local storage', function () {
271 it('should presist store', function (done) {
272 var app = express()
273 var cb = after(2, done)
274 var store = { foo: 'bar' }
276 app.use(function (req, res, next) {
277 req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
278 req.asyncLocalStorage.run(store, next)
281 app.use(function (req, res) {
282 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
283 if (err) return cb(err)
285 var local = req.asyncLocalStorage.getStore()
287 assert.strictEqual(local.foo, 'bar')
288 cb()
292 request(app)
293 .get('/')
294 .expect('Content-Type', 'text/plain; charset=utf-8')
295 .expect(200, 'tobi', cb)
298 it('should presist store on error', function (done) {
299 var app = express()
300 var store = { foo: 'bar' }
302 app.use(function (req, res, next) {
303 req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
304 req.asyncLocalStorage.run(store, next)
307 app.use(function (req, res) {
308 res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {
309 var local = req.asyncLocalStorage.getStore()
311 if (local) {
312 res.setHeader('x-store-foo', String(local.foo))
315 res.send(err ? 'got ' + err.status + ' error' : 'no error')
319 request(app)
320 .get('/')
321 .expect(200)
322 .expect('x-store-foo', 'bar')
323 .expect('got 404 error')
324 .end(done)
329 describe('.sendFile(path, options)', function () {
330 it('should pass options to send module', function (done) {
331 request(createApp(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 }))
332 .get('/')
333 .expect(200, 'to', done)
336 describe('with "acceptRanges" option', function () {
337 describe('when true', function () {
338 it('should advertise byte range accepted', function (done) {
339 var app = express()
341 app.use(function (req, res) {
342 res.sendFile(path.resolve(fixtures, 'nums.txt'), {
343 acceptRanges: true
347 request(app)
348 .get('/')
349 .expect(200)
350 .expect('Accept-Ranges', 'bytes')
351 .expect('123456789')
352 .end(done)
355 it('should respond to range request', function (done) {
356 var app = express()
358 app.use(function (req, res) {
359 res.sendFile(path.resolve(fixtures, 'nums.txt'), {
360 acceptRanges: true
364 request(app)
365 .get('/')
366 .set('Range', 'bytes=0-4')
367 .expect(206, '12345', done)
371 describe('when false', function () {
372 it('should not advertise accept-ranges', function (done) {
373 var app = express()
375 app.use(function (req, res) {
376 res.sendFile(path.resolve(fixtures, 'nums.txt'), {
377 acceptRanges: false
381 request(app)
382 .get('/')
383 .expect(200)
384 .expect(utils.shouldNotHaveHeader('Accept-Ranges'))
385 .end(done)
388 it('should not honor range requests', function (done) {
389 var app = express()
391 app.use(function (req, res) {
392 res.sendFile(path.resolve(fixtures, 'nums.txt'), {
393 acceptRanges: false
397 request(app)
398 .get('/')
399 .set('Range', 'bytes=0-4')
400 .expect(200, '123456789', done)
405 describe('with "cacheControl" option', function () {
406 describe('when true', function () {
407 it('should send cache-control header', function (done) {
408 var app = express()
410 app.use(function (req, res) {
411 res.sendFile(path.resolve(fixtures, 'user.html'), {
412 cacheControl: true
416 request(app)
417 .get('/')
418 .expect(200)
419 .expect('Cache-Control', 'public, max-age=0')
420 .end(done)
424 describe('when false', function () {
425 it('should not send cache-control header', function (done) {
426 var app = express()
428 app.use(function (req, res) {
429 res.sendFile(path.resolve(fixtures, 'user.html'), {
430 cacheControl: false
434 request(app)
435 .get('/')
436 .expect(200)
437 .expect(utils.shouldNotHaveHeader('Cache-Control'))
438 .end(done)
443 describe('with "dotfiles" option', function () {
444 describe('when "allow"', function () {
445 it('should allow dotfiles', function (done) {
446 var app = express()
448 app.use(function (req, res) {
449 res.sendFile(path.resolve(fixtures, '.name'), {
450 dotfiles: 'allow'
454 request(app)
455 .get('/')
456 .expect(200)
457 .expect(utils.shouldHaveBody(Buffer.from('tobi')))
458 .end(done)
462 describe('when "deny"', function () {
463 it('should deny dotfiles', function (done) {
464 var app = express()
466 app.use(function (req, res) {
467 res.sendFile(path.resolve(fixtures, '.name'), {
468 dotfiles: 'deny'
472 request(app)
473 .get('/')
474 .expect(403)
475 .expect(/Forbidden/)
476 .end(done)
480 describe('when "ignore"', function () {
481 it('should ignore dotfiles', function (done) {
482 var app = express()
484 app.use(function (req, res) {
485 res.sendFile(path.resolve(fixtures, '.name'), {
486 dotfiles: 'ignore'
490 request(app)
491 .get('/')
492 .expect(404)
493 .expect(/Not Found/)
494 .end(done)
499 describe('with "headers" option', function () {
500 it('should set headers on response', function (done) {
501 var app = express()
503 app.use(function (req, res) {
504 res.sendFile(path.resolve(fixtures, 'user.html'), {
505 headers: {
506 'X-Foo': 'Bar',
507 'X-Bar': 'Foo'
512 request(app)
513 .get('/')
514 .expect(200)
515 .expect('X-Foo', 'Bar')
516 .expect('X-Bar', 'Foo')
517 .end(done)
520 it('should use last header when duplicated', function (done) {
521 var app = express()
523 app.use(function (req, res) {
524 res.sendFile(path.resolve(fixtures, 'user.html'), {
525 headers: {
526 'X-Foo': 'Bar',
527 'x-foo': 'bar'
532 request(app)
533 .get('/')
534 .expect(200)
535 .expect('X-Foo', 'bar')
536 .end(done)
539 it('should override Content-Type', function (done) {
540 var app = express()
542 app.use(function (req, res) {
543 res.sendFile(path.resolve(fixtures, 'user.html'), {
544 headers: {
545 'Content-Type': 'text/x-custom'
550 request(app)
551 .get('/')
552 .expect(200)
553 .expect('Content-Type', 'text/x-custom')
554 .end(done)
557 it('should not set headers on 404', function (done) {
558 var app = express()
560 app.use(function (req, res) {
561 res.sendFile(path.resolve(fixtures, 'does-not-exist'), {
562 headers: {
563 'X-Foo': 'Bar'
568 request(app)
569 .get('/')
570 .expect(404)
571 .expect(utils.shouldNotHaveHeader('X-Foo'))
572 .end(done)
576 describe('with "immutable" option', function () {
577 describe('when true', function () {
578 it('should send cache-control header with immutable', function (done) {
579 var app = express()
581 app.use(function (req, res) {
582 res.sendFile(path.resolve(fixtures, 'user.html'), {
583 immutable: true
587 request(app)
588 .get('/')
589 .expect(200)
590 .expect('Cache-Control', 'public, max-age=0, immutable')
591 .end(done)
595 describe('when false', function () {
596 it('should not send cache-control header with immutable', function (done) {
597 var app = express()
599 app.use(function (req, res) {
600 res.sendFile(path.resolve(fixtures, 'user.html'), {
601 immutable: false
605 request(app)
606 .get('/')
607 .expect(200)
608 .expect('Cache-Control', 'public, max-age=0')
609 .end(done)
614 describe('with "lastModified" option', function () {
615 describe('when true', function () {
616 it('should send last-modified header', function (done) {
617 var app = express()
619 app.use(function (req, res) {
620 res.sendFile(path.resolve(fixtures, 'user.html'), {
621 lastModified: true
625 request(app)
626 .get('/')
627 .expect(200)
628 .expect(utils.shouldHaveHeader('Last-Modified'))
629 .end(done)
632 it('should conditionally respond with if-modified-since', function (done) {
633 var app = express()
635 app.use(function (req, res) {
636 res.sendFile(path.resolve(fixtures, 'user.html'), {
637 lastModified: true
641 request(app)
642 .get('/')
643 .set('If-Modified-Since', (new Date(Date.now() + 99999).toUTCString()))
644 .expect(304, done)
648 describe('when false', function () {
649 it('should not have last-modified header', function (done) {
650 var app = express()
652 app.use(function (req, res) {
653 res.sendFile(path.resolve(fixtures, 'user.html'), {
654 lastModified: false
658 request(app)
659 .get('/')
660 .expect(200)
661 .expect(utils.shouldNotHaveHeader('Last-Modified'))
662 .end(done)
665 it('should not honor if-modified-since', function (done) {
666 var app = express()
668 app.use(function (req, res) {
669 res.sendFile(path.resolve(fixtures, 'user.html'), {
670 lastModified: false
674 request(app)
675 .get('/')
676 .set('If-Modified-Since', (new Date(Date.now() + 99999).toUTCString()))
677 .expect(200)
678 .expect(utils.shouldNotHaveHeader('Last-Modified'))
679 .end(done)
684 describe('with "maxAge" option', function () {
685 it('should set cache-control max-age to milliseconds', function (done) {
686 var app = express()
688 app.use(function (req, res) {
689 res.sendFile(path.resolve(fixtures, 'user.html'), {
690 maxAge: 20000
694 request(app)
695 .get('/')
696 .expect(200)
697 .expect('Cache-Control', 'public, max-age=20')
698 .end(done)
701 it('should cap cache-control max-age to 1 year', function (done) {
702 var app = express()
704 app.use(function (req, res) {
705 res.sendFile(path.resolve(fixtures, 'user.html'), {
706 maxAge: 99999999999
710 request(app)
711 .get('/')
712 .expect(200)
713 .expect('Cache-Control', 'public, max-age=31536000')
714 .end(done)
717 it('should min cache-control max-age to 0', function (done) {
718 var app = express()
720 app.use(function (req, res) {
721 res.sendFile(path.resolve(fixtures, 'user.html'), {
722 maxAge: -20000
726 request(app)
727 .get('/')
728 .expect(200)
729 .expect('Cache-Control', 'public, max-age=0')
730 .end(done)
733 it('should floor cache-control max-age', function (done) {
734 var app = express()
736 app.use(function (req, res) {
737 res.sendFile(path.resolve(fixtures, 'user.html'), {
738 maxAge: 21911.23
742 request(app)
743 .get('/')
744 .expect(200)
745 .expect('Cache-Control', 'public, max-age=21')
746 .end(done)
749 describe('when cacheControl: false', function () {
750 it('should not send cache-control', function (done) {
751 var app = express()
753 app.use(function (req, res) {
754 res.sendFile(path.resolve(fixtures, 'user.html'), {
755 cacheControl: false,
756 maxAge: 20000
760 request(app)
761 .get('/')
762 .expect(200)
763 .expect(utils.shouldNotHaveHeader('Cache-Control'))
764 .end(done)
768 describe('when string', function () {
769 it('should accept plain number as milliseconds', function (done) {
770 var app = express()
772 app.use(function (req, res) {
773 res.sendFile(path.resolve(fixtures, 'user.html'), {
774 maxAge: '20000'
778 request(app)
779 .get('/')
780 .expect(200)
781 .expect('Cache-Control', 'public, max-age=20')
782 .end(done)
785 it('should accept suffix "s" for seconds', function (done) {
786 var app = express()
788 app.use(function (req, res) {
789 res.sendFile(path.resolve(fixtures, 'user.html'), {
790 maxAge: '20s'
794 request(app)
795 .get('/')
796 .expect(200)
797 .expect('Cache-Control', 'public, max-age=20')
798 .end(done)
801 it('should accept suffix "m" for minutes', function (done) {
802 var app = express()
804 app.use(function (req, res) {
805 res.sendFile(path.resolve(fixtures, 'user.html'), {
806 maxAge: '20m'
810 request(app)
811 .get('/')
812 .expect(200)
813 .expect('Cache-Control', 'public, max-age=1200')
814 .end(done)
817 it('should accept suffix "d" for days', function (done) {
818 var app = express()
820 app.use(function (req, res) {
821 res.sendFile(path.resolve(fixtures, 'user.html'), {
822 maxAge: '20d'
826 request(app)
827 .get('/')
828 .expect(200)
829 .expect('Cache-Control', 'public, max-age=1728000')
830 .end(done)
835 describe('with "root" option', function () {
836 it('should allow relative path', function (done) {
837 var app = express()
839 app.use(function (req, res) {
840 res.sendFile('name.txt', {
841 root: fixtures
845 request(app)
846 .get('/')
847 .expect(200, 'tobi', done)
850 it('should allow up within root', function (done) {
851 var app = express()
853 app.use(function (req, res) {
854 res.sendFile('fake/../name.txt', {
855 root: fixtures
859 request(app)
860 .get('/')
861 .expect(200, 'tobi', done)
864 it('should reject up outside root', function (done) {
865 var app = express()
867 app.use(function (req, res) {
868 res.sendFile('..' + path.sep + path.relative(path.dirname(fixtures), path.join(fixtures, 'name.txt')), {
869 root: fixtures
873 request(app)
874 .get('/')
875 .expect(403, done)
878 it('should reject reading outside root', function (done) {
879 var app = express()
881 app.use(function (req, res) {
882 res.sendFile('../name.txt', {
883 root: fixtures
887 request(app)
888 .get('/')
889 .expect(403, done)
895 function createApp(path, options, fn) {
896 var app = express();
898 app.use(function (req, res) {
899 res.sendFile(path, options, fn);
902 return app;
905 function tryRequire (name) {
906 try {
907 return require(name)
908 } catch (e) {
909 return {}