From 4d74808ab6655ef7c0fd8cf311bc632f4e58fc10 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 17 Jun 2026 16:56:31 +0200 Subject: [PATCH] fix: error on non-files --- .labrc.js | 7 +++++++ lib/fs.js | 6 ++++++ test/file.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 .labrc.js diff --git a/.labrc.js b/.labrc.js new file mode 100644 index 0000000..fe48a87 --- /dev/null +++ b/.labrc.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = { + 'coverage-predicates': { + win32: process.platform === 'win32' + } +}; diff --git a/lib/fs.js b/lib/fs.js index 94f0435..f4d419f 100755 --- a/lib/fs.js +++ b/lib/fs.js @@ -66,6 +66,12 @@ exports.File = class { throw Boom.forbidden(null, { code: 'EISDIR', path: this.path }); } + // $lab:coverage:off$ $if:win32$ + if (!stat.isFile()) { + throw Boom.forbidden(null, { code: 'EINVAL', path: this.path }); + } + // $lab:coverage:on$ + return stat; } catch (err) { diff --git a/test/file.js b/test/file.js index 7c7002f..1192575 100755 --- a/test/file.js +++ b/test/file.js @@ -278,6 +278,38 @@ describe('file', () => { expect(res.request.response._error.data.path).to.equal(Path.join(__dirname, '..', 'lib')); }); + it('returns a 403 when the file is not a regular file (eg. device)', { skip: process.platform === 'win32' }, async () => { + + const filename = '/dev/null'; // a character device, not a regular file + + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { file: { path: filename, confine: false } } }); + + const res = await server.inject('/'); + + expect(res.statusCode).to.equal(403); + expect(res.request.response._error.data.code).to.equal('EINVAL'); + expect(res.request.response._error.data.path).to.equal(filename); + }); + + it('returns a file through a symbolic link', { skip: process.platform === 'win32' }, async () => { + + const link = File.uniqueFilename(Os.tmpdir()) + '.package.json'; + Fs.symlinkSync(Path.join(__dirname, '..', 'package.json'), link); + + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { file: { path: link, confine: false } } }); + + const res = await server.inject('/'); + + Fs.unlinkSync(link); + + expect(res.statusCode).to.equal(200); + expect(res.payload).to.contain('hapi'); + expect(res.headers['content-type']).to.equal('application/json; charset=utf-8'); + expect(res.headers['content-length']).to.exist(); + }); + it('returns a file using the built-in handler config', async () => { const server = await provisionServer({ routes: { files: { relativeTo: Path.join(__dirname, '..') } } });