From 387499cf97b2fb1a85f6effff6b8464d3b186c68 Mon Sep 17 00:00:00 2001 From: nimesh0505 Date: Tue, 3 Sep 2024 14:58:41 +0530 Subject: [PATCH] default to CWD when serve:false and no root --- index.js | 45 +++++++++++---------- test/static.test.js | 97 ++++++++++++++++++++++++++++----------------- 2 files changed, 85 insertions(+), 57 deletions(-) diff --git a/index.js b/index.js index 0ed4b02..c186030 100644 --- a/index.js +++ b/index.js @@ -18,8 +18,13 @@ const supportedEncodings = ['br', 'gzip', 'deflate'] send.mime.default_type = 'application/octet-stream' async function fastifyStatic (fastify, opts) { + if (opts.serve === false && opts.root === undefined) { + opts.root = process.cwd() + fastify.log.warn('No root path provided. Defaulting to current working directory. This may pose security risks if not intended.') + } + opts.root = normalizeRoot(opts.root) - checkRootPathForErrors(fastify, opts.root, opts.serve) + checkRootPathForErrors(fastify, opts.root, opts.serve === false) const setHeaders = opts.setHeaders if (setHeaders !== undefined && typeof setHeaders !== 'function') { @@ -408,11 +413,7 @@ function normalizeRoot (root) { return root } -function checkRootPathForErrors (fastify, rootPath, serve = true) { - if (serve === false) { - return - } - +function checkRootPathForErrors (fastify, rootPath, skipExistenceCheck) { if (rootPath === undefined) { throw new Error('"root" option is required') } @@ -429,18 +430,18 @@ function checkRootPathForErrors (fastify, rootPath, serve = true) { } // check each path and fail at first invalid - rootPath.map((path) => checkPath(fastify, path)) + rootPath.map((path) => checkPath(fastify, path, skipExistenceCheck)) return } if (typeof rootPath === 'string') { - return checkPath(fastify, rootPath) + return checkPath(fastify, rootPath, skipExistenceCheck) } throw new Error('"root" option must be a string or array of strings') } -function checkPath (fastify, rootPath) { +function checkPath (fastify, rootPath, skipExistenceCheck) { if (typeof rootPath !== 'string') { throw new Error('"root" option must be a string') } @@ -448,21 +449,23 @@ function checkPath (fastify, rootPath) { throw new Error('"root" option must be an absolute path') } - let pathStat + if (!skipExistenceCheck) { + let pathStat - try { - pathStat = statSync(rootPath) - } catch (e) { - if (e.code === 'ENOENT') { - fastify.log.warn(`"root" path "${rootPath}" must exist`) - return - } + try { + pathStat = statSync(rootPath) + } catch (e) { + if (e.code === 'ENOENT') { + fastify.log.warn(`"root" path "${rootPath}" must exist`) + return + } - throw e - } + throw e + } - if (pathStat.isDirectory() === false) { - throw new Error('"root" option must point to a directory') + if (pathStat.isDirectory() === false) { + throw new Error('"root" option must point to a directory') + } } } diff --git a/test/static.test.js b/test/static.test.js index 678de91..c532dbe 100644 --- a/test/static.test.js +++ b/test/static.test.js @@ -4024,50 +4024,75 @@ t.test('content-length in head route should not return zero when using wildcard' }) }) -t.test('serve: false disables root path check', (t) => { - t.plan(3) +t.test('serve: false disables root path check', t => { + t.plan(4) - const pluginOptions = { - root: path.join(__dirname, '/static'), - prefix: '/static', - serve: false - } - const fastify = Fastify() - fastify.register(fastifyStatic, pluginOptions) + t.test('should default to CWD when no root is provided', async (t) => { + t.plan(3) + const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + let warningLogged = false + fastify.log.warn = (message) => { + if (message.includes('No root path provided')) { + warningLogged = true + } + } - fastify.listen({ port: 0 }, (err) => { - t.error(err) + await fastify.register(fastifyStatic, { serve: false }) - fastify.server.unref() + t.ok(warningLogged, 'should log a warning about defaulting to CWD') - t.test('/static/index.html', (t) => { - t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT) - simple.concat({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/static/index.html' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - genericErrorResponseChecks(t, response) - t.end() - }) + fastify.get('/test', (req, reply) => { + reply.sendFile('test/static/index.html') }) - t.test('/static/deep/path/for/test/purpose/foo.html', (t) => { - t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT) - simple.concat({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test/purpose/foo.html' - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 404) - genericErrorResponseChecks(t, response) - t.end() - }) + const response = await fastify.inject('/test') + t.equal(response.statusCode, 200, 'should serve file from CWD') + + const fs = require('fs') + const expectedContent = fs.readFileSync('test/static/index.html', 'utf8') + t.equal(response.payload, expectedContent, 'should serve correct file content') + }) + + t.test('should not throw when root is non-existent directory and serve is false', async (t) => { + t.plan(1) + const fastify = Fastify() + const nonExistentPath = path.join(__dirname, 'definitely-non-existent-directory') + + await fastify.register(fastifyStatic, { + serve: false, + root: nonExistentPath }) + t.pass('should not throw for non-existent directory') + }) - t.end() + t.test('should still validate root is a string or array of strings', async (t) => { + t.plan(1) + const fastify = Fastify() + + try { + await fastify.register(fastifyStatic, { + serve: false, + root: 123 + }) + t.fail('Should have thrown an error for non-string root') + } catch (error) { + t.match(error.message, /"root" option must be a string or array of strings/, 'Should throw for non-string root') + } + }) + + t.test('should still validate root is an absolute path', async (t) => { + t.plan(1) + const fastify = Fastify() + + try { + await fastify.register(fastifyStatic, { + serve: false, + root: 'relative/path' + }) + t.fail('Should have thrown an error for relative path') + } catch (error) { + t.match(error.message, /"root" option must be an absolute path/, 'Should throw for relative path') + } }) })