Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore!: bump dependency send@3 #455

Merged
merged 6 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ on:

jobs:
test:
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v4.1.0
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5.0.0
with:
license-check: true
lint: true
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
260 changes: 122 additions & 138 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,148 @@ 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': {
const path = metadata.path
if (opts.list) {
await dirList.send({
reply,
dir: path,
options: opts.list,
route: pathname,
prefix,
dotfiles: opts.dotfiles
}).catch((err) => reply.send(err))
}
cb()
}
})

wrap.getHeader = reply.getHeader.bind(reply)
wrap.setHeader = reply.header.bind(reply)
wrap.removeHeader = () => {}
wrap.finished = false
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
)
}

Object.defineProperty(wrap, 'filename', {
get () {
return resolvedFilename
}
})
Object.defineProperty(wrap, 'statusCode', {
get () {
return reply.raw.statusCode
},
set (code) {
reply.code(code)
reply.callNotFound()
}
break
}
})

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)
case 'error': {
if (
statusCode === 403 &&
(!options.index || !options.index.length) &&
pathnameForSend[pathnameForSend.length - 1] === '/'
) {
if (opts.list) {
await dirList.send({
reply,
dir: dirList.path(opts.root, pathname),
options: opts.list,
route: pathname,
prefix,
dotfiles: opts.dotfiles
}).catch((err) => reply.send(err))
}
}
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
}
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
}).catch((err) => reply.send(err))
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 +377,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 +531,7 @@ function getRedirectUrl (url) {
}

module.exports = fp(fastifyStatic, {
fastify: '4.x',
// fastify: '4.x',
name: '@fastify/static'
})
module.exports.default = fastifyStatic
Expand Down
6 changes: 3 additions & 3 deletions lib/dirList.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,17 @@ const dirList = {
entries.dirs.forEach(entry => nameEntries.dirs.push(entry.name))
entries.files.forEach(entry => nameEntries.files.push(entry.name))

reply.send(nameEntries)
await reply.send(nameEntries)
} else {
reply.send(entries)
await reply.send(entries)
}
return
}

const html = options.render(
entries.dirs.map(entry => dirList.htmlInfo(entry, route, prefix, options)),
entries.files.map(entry => dirList.htmlInfo(entry, route, prefix, options)))
reply.type('text/html').send(html)
await reply.type('text/html').send(html)
},

/**
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": "^3.1.0",
"content-disposition": "^0.5.4",
"fastify-plugin": "^5.0.0-pre.fv5.1",
"fastq": "^1.17.1",
Expand Down
Loading