Use let or const to avoid scope issues and hoisting
var after = require('after');
1'use strict'23var after = require('after');4var assert = require('node:assert')5var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage6const { Buffer } = require('node:buffer');78var express = require('../')9 , request = require('supertest')10var onFinished = require('on-finished');11var path = require('node:path');12var fixtures = path.join(__dirname, 'fixtures');13var utils = require('./support/utils');1415describe('res', function(){16 describe('.sendFile(path)', function () {17 it('should error missing path', function (done) {18 var app = createApp();1920 request(app)21 .get('/')22 .expect(500, /path.*required/, done);23 });2425 it('should error for non-string path', function (done) {26 var app = createApp(42)2728 request(app)29 .get('/')30 .expect(500, /TypeError: path must be a string to res.sendFile/, done)31 })3233 it('should error for non-absolute path', function (done) {34 var app = createApp('name.txt')3536 request(app)37 .get('/')38 .expect(500, /TypeError: path must be absolute/, done)39 })4041 it('should transfer a file', function (done) {42 var app = createApp(path.resolve(fixtures, 'name.txt'));4344 request(app)45 .get('/')46 .expect(200, 'tobi', done);47 });4849 it('should transfer a file with special characters in string', function (done) {50 var app = createApp(path.resolve(fixtures, '% of dogs.txt'));5152 request(app)53 .get('/')54 .expect(200, '20%', done);55 });5657 it('should include ETag', function (done) {58 var app = createApp(path.resolve(fixtures, 'name.txt'));5960 request(app)61 .get('/')62 .expect('ETag', /^(?:W\/)?"[^"]+"$/)63 .expect(200, 'tobi', done);64 });6566 it('should 304 when ETag matches', function (done) {67 var app = createApp(path.resolve(fixtures, 'name.txt'));6869 request(app)70 .get('/')71 .expect('ETag', /^(?:W\/)?"[^"]+"$/)72 .expect(200, 'tobi', function (err, res) {73 if (err) return done(err);74 var etag = res.headers.etag;75 request(app)76 .get('/')77 .set('If-None-Match', etag)78 .expect(304, done);79 });80 });8182 it('should disable the ETag function if requested', function (done) {83 var app = createApp(path.resolve(fixtures, 'name.txt')).disable('etag');8485 request(app)86 .get('/')87 .expect(handleHeaders)88 .expect(200, done);8990 function handleHeaders (res) {91 assert(res.headers.etag === undefined);92 }93 });9495 it('should 404 for directory', function (done) {96 var app = createApp(path.resolve(fixtures, 'blog'));9798 request(app)99 .get('/')100 .expect(404, done);101 });102103 it('should 404 when not found', function (done) {104 var app = createApp(path.resolve(fixtures, 'does-no-exist'));105106 app.use(function (req, res) {107 res.statusCode = 200;108 res.send('no!');109 });110111 request(app)112 .get('/')113 .expect(404, done);114 });115116 it('should send cache-control by default', function (done) {117 var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'))118119 request(app)120 .get('/')121 .expect('Cache-Control', 'public, max-age=0')122 .expect(200, done)123 })124125 it('should not serve dotfiles by default', function (done) {126 var app = createApp(path.resolve(__dirname, 'fixtures/.name'))127128 request(app)129 .get('/')130 .expect(404, done)131 })132133 it('should not override manual content-types', function (done) {134 var app = express();135136 app.use(function (req, res) {137 res.contentType('application/x-bogus');138 res.sendFile(path.resolve(fixtures, 'name.txt'));139 });140141 request(app)142 .get('/')143 .expect('Content-Type', 'application/x-bogus')144 .end(done);145 })146147 it('should not error if the client aborts', function (done) {148 var app = express();149 var cb = after(2, done)150 var error = null151152 app.use(function (req, res) {153 setImmediate(function () {154 res.sendFile(path.resolve(fixtures, 'name.txt'));155 setTimeout(function () {156 cb(error)157 }, 10)158 })159 test.req.abort()160 });161162 app.use(function (err, req, res, next) {163 error = err164 next(err)165 });166167 var server = app.listen()168 var test = request(server).get('/')169 test.end(function (err) {170 assert.ok(err)171 server.close(cb)172 })173 })174 })175176 describe('.sendFile(path, fn)', function () {177 it('should invoke the callback when complete', function (done) {178 var cb = after(2, done);179 var app = createApp(path.resolve(fixtures, 'name.txt'), cb);180181 request(app)182 .get('/')183 .expect(200, cb);184 })185186 it('should invoke the callback when client aborts', function (done) {187 var cb = after(2, done)188 var app = express();189190 app.use(function (req, res) {191 setImmediate(function () {192 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {193 assert.ok(err)194 assert.strictEqual(err.code, 'ECONNABORTED')195 cb()196 });197 });198 test.req.abort()199 });200201 var server = app.listen()202 var test = request(server).get('/')203 test.end(function (err) {204 assert.ok(err)205 server.close(cb)206 })207 })208209 it('should invoke the callback when client already aborted', function (done) {210 var cb = after(2, done)211 var app = express();212213 app.use(function (req, res) {214 onFinished(res, function () {215 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {216 assert.ok(err)217 assert.strictEqual(err.code, 'ECONNABORTED')218 cb()219 });220 });221 test.req.abort()222 });223224 var server = app.listen()225 var test = request(server).get('/')226 test.end(function (err) {227 assert.ok(err)228 server.close(cb)229 })230 })231232 it('should invoke the callback without error when HEAD', function (done) {233 var app = express();234 var cb = after(2, done);235236 app.use(function (req, res) {237 res.sendFile(path.resolve(fixtures, 'name.txt'), cb);238 });239240 request(app)241 .head('/')242 .expect(200, cb);243 });244245 it('should invoke the callback without error when 304', function (done) {246 var app = express();247 var cb = after(3, done);248249 app.use(function (req, res) {250 res.sendFile(path.resolve(fixtures, 'name.txt'), cb);251 });252253 request(app)254 .get('/')255 .expect('ETag', /^(?:W\/)?"[^"]+"$/)256 .expect(200, 'tobi', function (err, res) {257 if (err) return cb(err);258 var etag = res.headers.etag;259 request(app)260 .get('/')261 .set('If-None-Match', etag)262 .expect(304, cb);263 });264 });265266 it('should invoke the callback on 404', function(done){267 var app = express();268269 app.use(function (req, res) {270 res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {271 res.send(err ? 'got ' + err.status + ' error' : 'no error')272 });273 });274275 request(app)276 .get('/')277 .expect(200, 'got 404 error', done)278 })279280 describe('async local storage', function () {281 it('should persist store', function (done) {282 var app = express()283 var cb = after(2, done)284 var store = { foo: 'bar' }285286 app.use(function (req, res, next) {287 req.asyncLocalStorage = new AsyncLocalStorage()288 req.asyncLocalStorage.run(store, next)289 })290291 app.use(function (req, res) {292 res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {293 if (err) return cb(err)294295 var local = req.asyncLocalStorage.getStore()296297 assert.strictEqual(local.foo, 'bar')298 cb()299 })300 })301302 request(app)303 .get('/')304 .expect('Content-Type', 'text/plain; charset=utf-8')305 .expect(200, 'tobi', cb)306 })307308 it('should persist store on error', function (done) {309 var app = express()310 var store = { foo: 'bar' }311312 app.use(function (req, res, next) {313 req.asyncLocalStorage = new AsyncLocalStorage()314 req.asyncLocalStorage.run(store, next)315 })316317 app.use(function (req, res) {318 res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {319 var local = req.asyncLocalStorage.getStore()320321 if (local) {322 res.setHeader('x-store-foo', String(local.foo))323 }324325 res.send(err ? 'got ' + err.status + ' error' : 'no error')326 })327 })328329 request(app)330 .get('/')331 .expect(200)332 .expect('x-store-foo', 'bar')333 .expect('got 404 error')334 .end(done)335 })336 })337 })338339 describe('.sendFile(path, options)', function () {340 it('should pass options to send module', function (done) {341 request(createApp(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 }))342 .get('/')343 .expect(200, 'to', done)344 })345346 describe('with "acceptRanges" option', function () {347 describe('when true', function () {348 it('should advertise byte range accepted', function (done) {349 var app = express()350351 app.use(function (req, res) {352 res.sendFile(path.resolve(fixtures, 'nums.txt'), {353 acceptRanges: true354 })355 })356357 request(app)358 .get('/')359 .expect(200)360 .expect('Accept-Ranges', 'bytes')361 .expect('123456789')362 .end(done)363 })364365 it('should respond to range request', function (done) {366 var app = express()367368 app.use(function (req, res) {369 res.sendFile(path.resolve(fixtures, 'nums.txt'), {370 acceptRanges: true371 })372 })373374 request(app)375 .get('/')376 .set('Range', 'bytes=0-4')377 .expect(206, '12345', done)378 })379 })380381 describe('when false', function () {382 it('should not advertise accept-ranges', function (done) {383 var app = express()384385 app.use(function (req, res) {386 res.sendFile(path.resolve(fixtures, 'nums.txt'), {387 acceptRanges: false388 })389 })390391 request(app)392 .get('/')393 .expect(200)394 .expect(utils.shouldNotHaveHeader('Accept-Ranges'))395 .end(done)396 })397398 it('should not honor range requests', function (done) {399 var app = express()400401 app.use(function (req, res) {402 res.sendFile(path.resolve(fixtures, 'nums.txt'), {403 acceptRanges: false404 })405 })406407 request(app)408 .get('/')409 .set('Range', 'bytes=0-4')410 .expect(200, '123456789', done)411 })412 })413 })414415 describe('with "cacheControl" option', function () {416 describe('when true', function () {417 it('should send cache-control header', function (done) {418 var app = express()419420 app.use(function (req, res) {421 res.sendFile(path.resolve(fixtures, 'user.html'), {422 cacheControl: true423 })424 })425426 request(app)427 .get('/')428 .expect(200)429 .expect('Cache-Control', 'public, max-age=0')430 .end(done)431 })432 })433434 describe('when false', function () {435 it('should not send cache-control header', function (done) {436 var app = express()437438 app.use(function (req, res) {439 res.sendFile(path.resolve(fixtures, 'user.html'), {440 cacheControl: false441 })442 })443444 request(app)445 .get('/')446 .expect(200)447 .expect(utils.shouldNotHaveHeader('Cache-Control'))448 .end(done)449 })450 })451 })452453 describe('with "dotfiles" option', function () {454 describe('when "allow"', function () {455 it('should allow dotfiles', function (done) {456 var app = express()457458 app.use(function (req, res) {459 res.sendFile(path.resolve(fixtures, '.name'), {460 dotfiles: 'allow'461 })462 })463464 request(app)465 .get('/')466 .expect(200)467 .expect(utils.shouldHaveBody(Buffer.from('tobi')))468 .end(done)469 })470 })471472 describe('when "deny"', function () {473 it('should deny dotfiles', function (done) {474 var app = express()475476 app.use(function (req, res) {477 res.sendFile(path.resolve(fixtures, '.name'), {478 dotfiles: 'deny'479 })480 })481482 request(app)483 .get('/')484 .expect(403)485 .expect(/Forbidden/)486 .end(done)487 })488 })489490 describe('when "ignore"', function () {491 it('should ignore dotfiles', function (done) {492 var app = express()493494 app.use(function (req, res) {495 res.sendFile(path.resolve(fixtures, '.name'), {496 dotfiles: 'ignore'497 })498 })499500 request(app)501 .get('/')502 .expect(404)503 .expect(/Not Found/)504 .end(done)505 })506 })507 })508509 describe('with "headers" option', function () {510 it('should set headers on response', function (done) {511 var app = express()512513 app.use(function (req, res) {514 res.sendFile(path.resolve(fixtures, 'user.html'), {515 headers: {516 'X-Foo': 'Bar',517 'X-Bar': 'Foo'518 }519 })520 })521522 request(app)523 .get('/')524 .expect(200)525 .expect('X-Foo', 'Bar')526 .expect('X-Bar', 'Foo')527 .end(done)528 })529530 it('should use last header when duplicated', function (done) {531 var app = express()532533 app.use(function (req, res) {534 res.sendFile(path.resolve(fixtures, 'user.html'), {535 headers: {536 'X-Foo': 'Bar',537 'x-foo': 'bar'538 }539 })540 })541542 request(app)543 .get('/')544 .expect(200)545 .expect('X-Foo', 'bar')546 .end(done)547 })548549 it('should override Content-Type', function (done) {550 var app = express()551552 app.use(function (req, res) {553 res.sendFile(path.resolve(fixtures, 'user.html'), {554 headers: {555 'Content-Type': 'text/x-custom'556 }557 })558 })559560 request(app)561 .get('/')562 .expect(200)563 .expect('Content-Type', 'text/x-custom')564 .end(done)565 })566567 it('should not set headers on 404', function (done) {568 var app = express()569570 app.use(function (req, res) {571 res.sendFile(path.resolve(fixtures, 'does-not-exist'), {572 headers: {573 'X-Foo': 'Bar'574 }575 })576 })577578 request(app)579 .get('/')580 .expect(404)581 .expect(utils.shouldNotHaveHeader('X-Foo'))582 .end(done)583 })584 })585586 describe('with "immutable" option', function () {587 describe('when true', function () {588 it('should send cache-control header with immutable', function (done) {589 var app = express()590591 app.use(function (req, res) {592 res.sendFile(path.resolve(fixtures, 'user.html'), {593 immutable: true594 })595 })596597 request(app)598 .get('/')599 .expect(200)600 .expect('Cache-Control', 'public, max-age=0, immutable')601 .end(done)602 })603 })604605 describe('when false', function () {606 it('should not send cache-control header with immutable', function (done) {607 var app = express()608609 app.use(function (req, res) {610 res.sendFile(path.resolve(fixtures, 'user.html'), {611 immutable: false612 })613 })614615 request(app)616 .get('/')617 .expect(200)618 .expect('Cache-Control', 'public, max-age=0')619 .end(done)620 })621 })622 })623624 describe('with "lastModified" option', function () {625 describe('when true', function () {626 it('should send last-modified header', function (done) {627 var app = express()628629 app.use(function (req, res) {630 res.sendFile(path.resolve(fixtures, 'user.html'), {631 lastModified: true632 })633 })634635 request(app)636 .get('/')637 .expect(200)638 .expect(utils.shouldHaveHeader('Last-Modified'))639 .end(done)640 })641642 it('should conditionally respond with if-modified-since', function (done) {643 var app = express()644645 app.use(function (req, res) {646 res.sendFile(path.resolve(fixtures, 'user.html'), {647 lastModified: true648 })649 })650651 request(app)652 .get('/')653 .set('If-Modified-Since', (new Date(Date.now() + 99999).toUTCString()))654 .expect(304, done)655 })656 })657658 describe('when false', function () {659 it('should not have last-modified header', function (done) {660 var app = express()661662 app.use(function (req, res) {663 res.sendFile(path.resolve(fixtures, 'user.html'), {664 lastModified: false665 })666 })667668 request(app)669 .get('/')670 .expect(200)671 .expect(utils.shouldNotHaveHeader('Last-Modified'))672 .end(done)673 })674675 it('should not honor if-modified-since', function (done) {676 var app = express()677678 app.use(function (req, res) {679 res.sendFile(path.resolve(fixtures, 'user.html'), {680 lastModified: false681 })682 })683684 request(app)685 .get('/')686 .set('If-Modified-Since', (new Date(Date.now() + 99999).toUTCString()))687 .expect(200)688 .expect(utils.shouldNotHaveHeader('Last-Modified'))689 .end(done)690 })691 })692 })693694 describe('with "maxAge" option', function () {695 it('should set cache-control max-age to milliseconds', function (done) {696 var app = express()697698 app.use(function (req, res) {699 res.sendFile(path.resolve(fixtures, 'user.html'), {700 maxAge: 20000701 })702 })703704 request(app)705 .get('/')706 .expect(200)707 .expect('Cache-Control', 'public, max-age=20')708 .end(done)709 })710711 it('should cap cache-control max-age to 1 year', function (done) {712 var app = express()713714 app.use(function (req, res) {715 res.sendFile(path.resolve(fixtures, 'user.html'), {716 maxAge: 99999999999717 })718 })719720 request(app)721 .get('/')722 .expect(200)723 .expect('Cache-Control', 'public, max-age=31536000')724 .end(done)725 })726727 it('should min cache-control max-age to 0', function (done) {728 var app = express()729730 app.use(function (req, res) {731 res.sendFile(path.resolve(fixtures, 'user.html'), {732 maxAge: -20000733 })734 })735736 request(app)737 .get('/')738 .expect(200)739 .expect('Cache-Control', 'public, max-age=0')740 .end(done)741 })742743 it('should floor cache-control max-age', function (done) {744 var app = express()745746 app.use(function (req, res) {747 res.sendFile(path.resolve(fixtures, 'user.html'), {748 maxAge: 21911.23749 })750 })751752 request(app)753 .get('/')754 .expect(200)755 .expect('Cache-Control', 'public, max-age=21')756 .end(done)757 })758759 describe('when cacheControl: false', function () {760 it('should not send cache-control', function (done) {761 var app = express()762763 app.use(function (req, res) {764 res.sendFile(path.resolve(fixtures, 'user.html'), {765 cacheControl: false,766 maxAge: 20000767 })768 })769770 request(app)771 .get('/')772 .expect(200)773 .expect(utils.shouldNotHaveHeader('Cache-Control'))774 .end(done)775 })776 })777778 describe('when string', function () {779 it('should accept plain number as milliseconds', function (done) {780 var app = express()781782 app.use(function (req, res) {783 res.sendFile(path.resolve(fixtures, 'user.html'), {784 maxAge: '20000'785 })786 })787788 request(app)789 .get('/')790 .expect(200)791 .expect('Cache-Control', 'public, max-age=20')792 .end(done)793 })794795 it('should accept suffix "s" for seconds', function (done) {796 var app = express()797798 app.use(function (req, res) {799 res.sendFile(path.resolve(fixtures, 'user.html'), {800 maxAge: '20s'801 })802 })803804 request(app)805 .get('/')806 .expect(200)807 .expect('Cache-Control', 'public, max-age=20')808 .end(done)809 })810811 it('should accept suffix "m" for minutes', function (done) {812 var app = express()813814 app.use(function (req, res) {815 res.sendFile(path.resolve(fixtures, 'user.html'), {816 maxAge: '20m'817 })818 })819820 request(app)821 .get('/')822 .expect(200)823 .expect('Cache-Control', 'public, max-age=1200')824 .end(done)825 })826827 it('should accept suffix "d" for days', function (done) {828 var app = express()829830 app.use(function (req, res) {831 res.sendFile(path.resolve(fixtures, 'user.html'), {832 maxAge: '20d'833 })834 })835836 request(app)837 .get('/')838 .expect(200)839 .expect('Cache-Control', 'public, max-age=1728000')840 .end(done)841 })842 })843 })844845 describe('with "root" option', function () {846 it('should allow relative path', function (done) {847 var app = express()848849 app.use(function (req, res) {850 res.sendFile('name.txt', {851 root: fixtures852 })853 })854855 request(app)856 .get('/')857 .expect(200, 'tobi', done)858 })859860 it('should allow up within root', function (done) {861 var app = express()862863 app.use(function (req, res) {864 res.sendFile('fake/../name.txt', {865 root: fixtures866 })867 })868869 request(app)870 .get('/')871 .expect(200, 'tobi', done)872 })873874 it('should reject up outside root', function (done) {875 var app = express()876877 app.use(function (req, res) {878 res.sendFile('..' + path.sep + path.relative(path.dirname(fixtures), path.join(fixtures, 'name.txt')), {879 root: fixtures880 })881 })882883 request(app)884 .get('/')885 .expect(403, done)886 })887888 it('should reject reading outside root', function (done) {889 var app = express()890891 app.use(function (req, res) {892 res.sendFile('../name.txt', {893 root: fixtures894 })895 })896897 request(app)898 .get('/')899 .expect(403, done)900 })901 })902 })903})904905function createApp(path, options, fn) {906 var app = express();907908 app.use(function (req, res) {909 res.sendFile(path, options, fn);910 });911912 return app;913}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.