diff --git a/index.js b/index.js index b22a4f99..cddb54c8 100644 --- a/index.js +++ b/index.js @@ -92,7 +92,11 @@ async function fastifyStatic (fastify, opts) { }) wrap.on('pipe', function () { - reply.send(wrap) + if (request.method !== 'HEAD') { reply.send(wrap) } + }) + + wrap.on('finish', function () { + if (request.method === 'HEAD') { reply.send() } }) if (setHeaders !== undefined) { @@ -182,6 +186,9 @@ async function fastifyStatic (fastify, opts) { if (opts.serve !== false) { if (opts.wildcard && typeof opts.wildcard !== 'boolean') throw new Error('"wildcard" option must be a boolean') if (opts.wildcard === undefined || opts.wildcard === true) { + fastify.head(prefix + '*', routeOpts, function (req, reply) { + pumpSendToReply(req, reply, '/' + req.params['*'], sendOptions.root) + }) fastify.get(prefix + '*', routeOpts, function (req, reply) { pumpSendToReply(req, reply, '/' + req.params['*'], sendOptions.root) }) @@ -203,6 +210,10 @@ async function fastifyStatic (fastify, opts) { for (let file of files) { file = file.replace(rootPath.replace(/\\/g, '/'), '').replace(/^\//, '') const route = encodeURI(prefix + file).replace(/\/\//g, '/') + fastify.head(route, routeOpts, function (req, reply) { + pumpSendToReply(req, reply, '/' + file, rootPath) + }) + fastify.get(route, routeOpts, function (req, reply) { pumpSendToReply(req, reply, '/' + file, rootPath) }) @@ -216,11 +227,18 @@ async function fastifyStatic (fastify, opts) { const pathname = dirname + (dirname.endsWith('/') ? '' : '/') const file = '/' + pathname.replace(prefix, '') + fastify.head(pathname, routeOpts, function (req, reply) { + pumpSendToReply(req, reply, file, rootPath) + }) + fastify.get(pathname, routeOpts, function (req, reply) { pumpSendToReply(req, reply, file, rootPath) }) if (opts.redirect === true) { + fastify.head(pathname.replace(/\/$/, ''), routeOpts, function (req, reply) { + pumpSendToReply(req, reply, file.replace(/\/$/, ''), rootPath) + }) fastify.get(pathname.replace(/\/$/, ''), routeOpts, function (req, reply) { pumpSendToReply(req, reply, file.replace(/\/$/, ''), rootPath) }) diff --git a/test/static.test.js b/test/static.test.js index 9d5808c3..dbcc6ac1 100644 --- a/test/static.test.js +++ b/test/static.test.js @@ -40,7 +40,7 @@ function genericErrorResponseChecks (t, response) { } t.test('register /static prefixAvoidTrailingSlash', t => { - t.plan(11) + t.plan(12) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -184,6 +184,19 @@ t.test('register /static prefixAvoidTrailingSlash', t => { t.strictEqual(response.statusCode, 404) }) }) + + t.test('file not exposed outside of the plugin', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/static/index.html' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) }) }) @@ -333,7 +346,7 @@ t.test('register /static', t => { }) t.test('register /static/', t => { - t.plan(11) + t.plan(12) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -362,6 +375,19 @@ t.test('register /static/', t => { }) }) + t.test('/static/index.html', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/static/index.html' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) + t.test('/static/index.css', t => { t.plan(2 + GENERIC_RESPONSE_CHECK_COUNT) simple.concat({ @@ -1472,7 +1498,7 @@ t.test('with fastify-compress', t => { }) t.test('register /static/ with schemaHide true', t => { - t.plan(3) + t.plan(4) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -1512,7 +1538,7 @@ t.test('register /static/ with schemaHide true', t => { }) t.test('register /static/ with schemaHide false', t => { - t.plan(3) + t.plan(4) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -1552,7 +1578,7 @@ t.test('register /static/ with schemaHide false', t => { }) t.test('register /static/ without schemaHide', t => { - t.plan(3) + t.plan(4) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -1590,8 +1616,44 @@ t.test('register /static/ without schemaHide', t => { }) }) +t.test('fastify with exposeHeadRoutes', t => { + t.plan(2) + + const pluginOptions = { + root: path.join(__dirname, '/static'), + wildcard: false + } + const fastify = Fastify({ exposeHeadRoutes: true }) + fastify.register(fastifyStatic, pluginOptions) + + fastify.get('/*', (request, reply) => { + reply.send({ hello: 'world' }) + }) + + t.tearDown(fastify.close.bind(fastify)) + + fastify.listen(0, err => { + t.error(err) + + fastify.server.unref() + + t.test('/index.html', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/index.html' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) + }) +}) + t.test('register with wildcard false', t => { - t.plan(8) + t.plan(9) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -1699,6 +1761,19 @@ t.test('register with wildcard false', t => { t.deepEqual(JSON.parse(body), { hello: 'world' }) }) }) + + t.test('/index.css', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/index.css' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) }) }) @@ -1745,7 +1820,7 @@ t.test('register with wildcard string on multiple root paths', t => { }) t.test('register with wildcard false and alternative index', t => { - t.plan(8) + t.plan(11) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -1779,6 +1854,19 @@ t.test('register with wildcard false and alternative index', t => { }) }) + t.test('/index.html', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/index.html' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) + t.test('/index.css', t => { t.plan(2 + GENERIC_RESPONSE_CHECK_COUNT) simple.concat({ @@ -1804,6 +1892,19 @@ t.test('register with wildcard false and alternative index', t => { }) }) + t.test('/?a=b', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) + t.test('/not-defined', t => { t.plan(3) simple.concat({ @@ -1842,6 +1943,19 @@ t.test('register with wildcard false and alternative index', t => { }) }) + t.test('/deep/path/for/test/', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/deep/path/for/test/' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) + t.test('/../index.js', t => { t.plan(3) simple.concat({ @@ -1858,7 +1972,7 @@ t.test('register with wildcard false and alternative index', t => { }) t.test('register /static with wildcard false and alternative index', t => { - t.plan(9) + t.plan(11) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -1893,6 +2007,19 @@ t.test('register /static with wildcard false and alternative index', t => { }) }) + t.test('/static/index.html', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/static/index.html' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) + t.test('/static/index.css', t => { t.plan(2 + GENERIC_RESPONSE_CHECK_COUNT) simple.concat({ @@ -1938,6 +2065,19 @@ t.test('register /static with wildcard false and alternative index', t => { }) }) + t.test('/static/', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/static/' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) + t.test('/static/not-defined', t => { t.plan(3) simple.concat({ @@ -2103,7 +2243,7 @@ t.test('register /static with redirect true', t => { }) t.test('register /static with redirect true and wildcard false', t => { - t.plan(6) + t.plan(8) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -2161,6 +2301,20 @@ t.test('register /static with redirect true and wildcard false', t => { }) }) + t.test('/static/?a=b', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/static/?a=b' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) + t.test('/static/deep', t => { t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT) @@ -2211,6 +2365,20 @@ t.test('register /static with redirect true and wildcard false', t => { genericResponseChecks(t, response) }) }) + + t.test('/static/deep/path/for/test', t => { + t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) + + simple.concat({ + method: 'HEAD', + url: 'http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test' + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(body.toString(), '') + genericResponseChecks(t, response) + }) + }) }) }) @@ -2441,7 +2609,7 @@ t.test('inject support', async (t) => { }) t.test('routes should use custom errorHandler premature stream close', t => { - t.plan(3) + t.plan(4) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -2473,7 +2641,7 @@ t.test('routes should use custom errorHandler premature stream close', t => { }) t.test('routes should fallback to default errorHandler', t => { - t.plan(3) + t.plan(4) const pluginOptions = { root: path.join(__dirname, '/static'), @@ -2510,7 +2678,7 @@ t.test('routes should fallback to default errorHandler', t => { }) t.test('routes use default errorHandler when fastify.errorHandler is not defined', t => { - t.plan(3) + t.plan(4) const pluginOptions = { root: path.join(__dirname, '/static'),