Skip to content

Commit

Permalink
chore!: bump dependency send@3
Browse files Browse the repository at this point in the history
  • Loading branch information
climba03003 committed Jul 12, 2024
1 parent fc88938 commit 34222a3
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 147 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,13 +477,13 @@ If an error occurs while trying to send a file, the error will be passed
to Fastify's error handler. You can set a custom error handler with
[`fastify.setErrorHandler()`](https://fastify.dev/docs/latest/Reference/Server/#seterrorhandler).

### Payload `stream.filename`
### Payload `stream.path`

If you need to access the filename inside the `onSend` hook, you can use `payload.filename`.
If you need to access the file path inside the `onSend` hook, you can use `payload.path`.

```js
fastify.addHook('onSend', function (req, reply, payload, next) {
console.log(payload.filename)
console.log(payload.path)
next()
})
```
Expand Down
244 changes: 105 additions & 139 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict'

const { PassThrough } = require('node:stream')
const path = require('node:path')
const { fileURLToPath } = require('node:url')
const { statSync } = require('node:fs')
Expand Down Expand Up @@ -170,7 +169,7 @@ async function fastifyStatic (fastify, opts) {

const allowedPath = opts.allowedPath

function pumpSendToReply (
async function pumpSendToReply (
request,
reply,
pathname,
Expand Down Expand Up @@ -222,163 +221,130 @@ async function fastifyStatic (fastify, opts) {
}

// `send(..., path, ...)` will URI-decode path so we pass an encoded path here
const stream = send(request.raw, encodeURI(pathnameForSend), options)
let resolvedFilename
stream.on('file', function (file) {
resolvedFilename = file
})

const wrap = new PassThrough({
flush (cb) {
this.finished = true
if (reply.raw.statusCode === 304) {
reply.send('')
const {
statusCode,
headers,
stream,
type,
metadata
} = await send(request.raw, encodeURI(pathnameForSend), options)
switch (type) {
case 'directory': {
if (opts.list) {
await dirList.send({
reply,
dir: path,
options: opts.list,
route: pathname,
prefix,
dotfiles: opts.dotfiles
})
}
cb()
}
})

wrap.getHeader = reply.getHeader.bind(reply)
wrap.setHeader = reply.header.bind(reply)
wrap.removeHeader = () => {}
wrap.finished = false

Object.defineProperty(wrap, 'filename', {
get () {
return resolvedFilename
}
})
Object.defineProperty(wrap, 'statusCode', {
get () {
return reply.raw.statusCode
},
set (code) {
reply.code(code)
}
})
if (opts.redirect === true) {
try {
reply.redirect(301, getRedirectUrl(request.raw.url))
} /* c8 ignore start */ catch (error) {
// the try-catch here is actually unreachable, but we keep it for safety and prevent DoS attack
await reply.send(error)
} /* c8 ignore stop */
} else {
// if is a directory path without a trailing slash, and has an index file, reply as if it has a trailing slash
if (!pathname.endsWith('/') && findIndexFile(pathname, options.root, options.index)) {
return pumpSendToReply(
request,
reply,
pathname + '/',
rootPath,
undefined,
undefined,
checkedEncodings
)
}

if (request.method === 'HEAD') {
wrap.on('finish', reply.send.bind(reply))
} else {
wrap.on('pipe', function () {
if (encoding) {
reply.header('content-type', getContentType(pathname))
reply.header('content-encoding', encoding)
reply.callNotFound()
}
reply.send(wrap)
})
}

if (setHeaders !== undefined) {
stream.on('headers', setHeaders)
}

stream.on('directory', function (_, path) {
if (opts.list) {
dirList.send({
reply,
dir: path,
options: opts.list,
route: pathname,
prefix,
dotfiles: opts.dotfiles
}).catch((err) => reply.send(err))
return
break
}
case 'error': {
if (metadata.error.code === 'ENOENT') {
// when preCompress is enabled and the path is a directory without a trailing slash
if (opts.preCompressed && encoding) {
const indexPathname = findIndexFile(pathname, options.root, options.index)
if (indexPathname) {
return pumpSendToReply(
request,
reply,
pathname + '/',
rootPath,
undefined,
undefined,
checkedEncodings
)
}
}

if (opts.redirect === true) {
try {
reply.redirect(301, getRedirectUrl(request.raw.url))
} /* c8 ignore start */ catch (error) {
// the try-catch here is actually unreachable, but we keep it for safety and prevent DoS attack
reply.send(error)
} /* c8 ignore stop */
} else {
// if is a directory path without a trailing slash, and has an index file, reply as if it has a trailing slash
if (!pathname.endsWith('/') && findIndexFile(pathname, options.root, options.index)) {
return pumpSendToReply(
request,
reply,
pathname + '/',
rootPath,
undefined,
undefined,
checkedEncodings
)
}
// if file exists, send real file, otherwise send dir list if name match
if (opts.list && dirList.handle(pathname, opts.list)) {
await dirList.send({
reply,
dir: dirList.path(opts.root, pathname),
options: opts.list,
route: pathname,
prefix,
dotfiles: opts.dotfiles
})
return
}

reply.callNotFound()
}
})
// root paths left to try?
if (Array.isArray(rootPath) && rootPathOffset < (rootPath.length - 1)) {
return pumpSendToReply(request, reply, pathname, rootPath, rootPathOffset + 1)
}

stream.on('error', function (err) {
if (err.code === 'ENOENT') {
// when preCompress is enabled and the path is a directory without a trailing slash
if (opts.preCompressed && encoding) {
const indexPathname = findIndexFile(pathname, options.root, options.index)
if (indexPathname) {
if (opts.preCompressed && !checkedEncodings.has(encoding)) {
checkedEncodings.add(encoding)
return pumpSendToReply(
request,
reply,
pathname + '/',
pathnameOrig,
rootPath,
undefined,
rootPathOffset,
undefined,
checkedEncodings
)
}
}

// if file exists, send real file, otherwise send dir list if name match
if (opts.list && dirList.handle(pathname, opts.list)) {
dirList.send({
reply,
dir: dirList.path(opts.root, pathname),
options: opts.list,
route: pathname,
prefix,
dotfiles: opts.dotfiles
}).catch((err) => reply.send(err))
return
}

// root paths left to try?
if (Array.isArray(rootPath) && rootPathOffset < (rootPath.length - 1)) {
return pumpSendToReply(request, reply, pathname, rootPath, rootPathOffset + 1)
return reply.callNotFound()
}

if (opts.preCompressed && !checkedEncodings.has(encoding)) {
checkedEncodings.add(encoding)
return pumpSendToReply(
request,
reply,
pathnameOrig,
rootPath,
rootPathOffset,
undefined,
checkedEncodings
)
// The `send` library terminates the request with a 404 if the requested
// path contains a dotfile and `send` is initialized with `{dotfiles:
// 'ignore'}`. `send` aborts the request before getting far enough to
// check if the file exists (hence, a 404 `NotFoundError` instead of
// `ENOENT`).
// https://github.com/pillarjs/send/blob/de073ed3237ade9ff71c61673a34474b30e5d45b/index.js#L582
if (metadata.error.status === 404) {
return reply.callNotFound()
}

return reply.callNotFound()
await reply.send(metadata.error)
break
}

// The `send` library terminates the request with a 404 if the requested
// path contains a dotfile and `send` is initialized with `{dotfiles:
// 'ignore'}`. `send` aborts the request before getting far enough to
// check if the file exists (hence, a 404 `NotFoundError` instead of
// `ENOENT`).
// https://github.com/pillarjs/send/blob/de073ed3237ade9ff71c61673a34474b30e5d45b/index.js#L582
if (err.status === 404) {
return reply.callNotFound()
case 'file': {
reply.code(statusCode)
if (setHeaders !== undefined) {
setHeaders(reply.raw, metadata.path, metadata.stat)
}
reply.headers(headers)
if (encoding) {
reply.header('content-type', getContentType(pathname))
reply.header('content-encoding', encoding)
}
await reply.send(stream)
break
}

reply.send(err)
})

// we cannot use pump, because send error
// handling is not compatible
stream.pipe(wrap)
}
}

function setUpHeadAndGet (routeOpts, route, file, rootPath) {
Expand All @@ -393,11 +359,11 @@ async function fastifyStatic (fastify, opts) {
fastify.route(toSetUp)
}

function serveFileHandler (req, reply) {
async function serveFileHandler (req, reply) {
// TODO: remove the fallback branch when bump major
/* c8 ignore next */
const routeConfig = req.routeOptions?.config || req.routeConfig
pumpSendToReply(req, reply, routeConfig.file, routeConfig.rootPath)
return pumpSendToReply(req, reply, routeConfig.file, routeConfig.rootPath)
}
}

Expand Down Expand Up @@ -547,7 +513,7 @@ function getRedirectUrl (url) {
}

module.exports = fp(fastifyStatic, {
fastify: '4.x',
// fastify: '4.x',
name: '@fastify/static'
})
module.exports.default = fastifyStatic
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"homepage": "https://github.com/fastify/fastify-static",
"dependencies": {
"@fastify/accept-negotiator": "^2.0.0-pre.fv5.1",
"@fastify/send": "^3.0.0-pre.fv5.1",
"@fastify/send": "github:fastify/send#extends-result",
"content-disposition": "^0.5.4",
"fastify-plugin": "^5.0.0-pre.fv5.1",
"fastq": "^1.17.1",
Expand Down
8 changes: 4 additions & 4 deletions test/static.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ t.test('register /static with constraints', (t) => {
})
})

t.test('payload.filename is set', (t) => {
t.test('payload.path is set', (t) => {
t.plan(3)

const pluginOptions = {
Expand All @@ -692,7 +692,7 @@ t.test('payload.filename is set', (t) => {
let gotFilename
fastify.register(fastifyStatic, pluginOptions)
fastify.addHook('onSend', function (req, reply, payload, next) {
gotFilename = payload.filename
gotFilename = payload.path
next()
})

Expand Down Expand Up @@ -1361,13 +1361,13 @@ t.test('root not found warning', (t) => {
const destination = concat((data) => {
t.equal(JSON.parse(data).msg, `"root" path "${rootPath}" must exist`)
})
const logger = pino(
const loggerInstance = pino(
{
level: 'warn'
},
destination
)
const fastify = Fastify({ logger })
const fastify = Fastify({ loggerInstance })
fastify.register(fastifyStatic, pluginOptions)
fastify.listen({ port: 0 }, (err) => {
t.error(err)
Expand Down

0 comments on commit 34222a3

Please sign in to comment.