Skip to content

Commit

Permalink
default to CWD when serve:false and no root
Browse files Browse the repository at this point in the history
  • Loading branch information
nimesh0505 committed Sep 3, 2024
1 parent 8c39dca commit 387499c
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 57 deletions.
45 changes: 24 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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')
}
Expand All @@ -429,40 +430,42 @@ 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')
}
if (path.isAbsolute(rootPath) === false) {
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')
}
}
}

Expand Down
97 changes: 61 additions & 36 deletions test/static.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
})
})

0 comments on commit 387499c

Please sign in to comment.