Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fastify-static

Package Overview
Dependencies
Maintainers
13
Versions
55
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fastify-static - npm Package Compare versions

Comparing version 4.0.1 to 4.2.0

test/static-pre-compressed/all-three.html

5

index.d.ts

@@ -61,2 +61,7 @@ // Definitions by: Jannik <https://github.com/jannikkeye>

allowedPath?: (pathName: string, root?: string) => boolean;
/**
* @description
* Opt-in to looking for pre-compressed files
*/
preCompressed?: boolean;

@@ -63,0 +68,0 @@ // Passed on to `send`

225

index.js

@@ -13,2 +13,3 @@ 'use strict'

const globPromise = util.promisify(glob)
const encodingNegotiator = require('encoding-negotiator')

@@ -46,3 +47,11 @@ const dirList = require('./lib/dirList')

function pumpSendToReply (request, reply, pathname, rootPath, rootPathOffset = 0, pumpOptions = {}) {
function pumpSendToReply (
request,
reply,
pathname,
rootPath,
rootPathOffset = 0,
pumpOptions = {},
checkedExtensions
) {
const options = Object.assign({}, sendOptions, pumpOptions)

@@ -62,3 +71,22 @@

const stream = send(request.raw, pathname, options)
let encodingExt
let pathnameForSend = pathname
if (opts.preCompressed) {
/**
* We conditionally create this structure to track our attempts
* at sending pre-compressed assets
*/
if (!checkedExtensions) {
checkedExtensions = new Set()
}
encodingExt = checkEncodingHeaders(request.headers, checkedExtensions)
if (encodingExt) {
pathnameForSend = pathname + '.' + encodingExt
}
}
const stream = send(request.raw, pathnameForSend, options)
let resolvedFilename

@@ -97,5 +125,12 @@ stream.on('file', function (file) {

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

@@ -108,3 +143,8 @@ if (setHeaders !== undefined) {

if (opts.list) {
return dirList.send({ reply, dir: path, options: opts.list, route: pathname })
return dirList.send({
reply,
dir: path,
options: opts.list,
route: pathname
})
}

@@ -122,18 +162,29 @@

stream.on('error', function (err) {
if (err) {
if (err.code === 'ENOENT') {
// if file exists, send real file, otherwise send dir list if name match
if (opts.list && dirList.handle(pathname, opts.list)) {
return dirList.send({ reply, dir: dirList.path(opts.root, pathname), options: opts.list, route: pathname })
}
if (err.code === 'ENOENT') {
// if file exists, send real file, otherwise send dir list if name match
if (opts.list && dirList.handle(pathname, opts.list)) {
return dirList.send({ reply, dir: dirList.path(opts.root, pathname), options: opts.list, route: pathname })
}
// root paths left to try?
if (Array.isArray(rootPath) && rootPathOffset < (rootPath.length - 1)) {
return pumpSendToReply(request, reply, pathname, rootPath, rootPathOffset + 1)
}
// 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 && !checkedExtensions.has(encodingExt)) {
checkedExtensions.add(encodingExt)
return pumpSendToReply(
request,
reply,
pathname,
rootPath,
undefined,
undefined,
checkedExtensions
)
}
reply.send(err)
return reply.callNotFound()
}
reply.send(err)
})

@@ -151,3 +202,6 @@

if (!opts.prefixAvoidTrailingSlash) {
prefix = opts.prefix[opts.prefix.length - 1] === '/' ? opts.prefix : (opts.prefix + '/')
prefix =
opts.prefix[opts.prefix.length - 1] === '/'
? opts.prefix
: opts.prefix + '/'
}

@@ -166,3 +220,5 @@

const routeOpts = {
schema: { hide: typeof opts.schemaHide !== 'undefined' ? opts.schemaHide : true },
schema: {
hide: typeof opts.schemaHide !== 'undefined' ? opts.schemaHide : true
},
errorHandler: fastify.errorHandler ? errorHandler : undefined

@@ -173,22 +229,36 @@ }

fastify.decorateReply('sendFile', function (filePath, rootPath) {
pumpSendToReply(this.request, this, filePath, rootPath)
pumpSendToReply(
this.request,
this,
filePath,
rootPath || sendOptions.root
)
return this
})
fastify.decorateReply('download', function (filePath, fileName, options = {}) {
const { root, ...opts } = typeof fileName === 'object' ? fileName : options
fileName = typeof fileName === 'string' ? fileName : filePath
fastify.decorateReply(
'download',
function (filePath, fileName, options = {}) {
const { root, ...opts } =
typeof fileName === 'object' ? fileName : options
fileName = typeof fileName === 'string' ? fileName : filePath
// Set content disposition header
this.header('content-disposition', contentDisposition(fileName))
// Set content disposition header
this.header('content-disposition', contentDisposition(fileName))
pumpSendToReply(this.request, this, filePath, root, 0, opts)
pumpSendToReply(this.request, this, filePath, root, 0, opts)
return this
})
return this
}
)
}
if (opts.serve !== false) {
if (opts.wildcard && typeof opts.wildcard !== 'boolean') throw new Error('"wildcard" option must be a boolean')
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) {

@@ -206,11 +276,22 @@ pumpSendToReply(req, reply, '/' + req.params['*'], sendOptions.root)

const globPattern = '**/*'
const indexDirs = new Map()
const routes = new Set()
async function addGlobRoutes (rootPath) {
for (const rootPath of Array.isArray(sendOptions.root) ? sendOptions.root : [sendOptions.root]) {
const files = await globPromise(path.join(rootPath, globPattern), { nodir: true })
const indexDirs = new Set()
const indexes = typeof opts.index === 'undefined' ? ['index.html'] : [].concat(opts.index || [])
const indexes = typeof opts.index === 'undefined' ? ['index.html'] : [].concat(opts.index)
for (let file of files) {
file = file.replace(rootPath.replace(/\\/g, '/'), '').replace(/^\//, '')
file = file
.replace(rootPath.replace(/\\/g, '/'), '')
.replace(/^\//, '')
const route = encodeURI(prefix + file).replace(/\/\//g, '/')
if (routes.has(route)) {
continue
}
routes.add(route)
fastify.head(route, routeOpts, function (req, reply) {
pumpSendToReply(req, reply, '/' + file, rootPath)
})
fastify.get(route, routeOpts, function (req, reply) {

@@ -220,27 +301,29 @@ pumpSendToReply(req, reply, '/' + file, rootPath)

if (indexes.includes(path.posix.basename(route))) {
indexDirs.add(path.posix.dirname(route))
const key = path.posix.basename(route)
if (indexes.includes(key) && !indexDirs.has(key)) {
indexDirs.set(path.posix.dirname(route), rootPath)
}
}
}
indexDirs.forEach(function (dirname) {
const pathname = dirname + (dirname.endsWith('/') ? '' : '/')
const file = '/' + pathname.replace(prefix, '')
for (const [dirname, rootPath] of indexDirs.entries()) {
const pathname = dirname + (dirname.endsWith('/') ? '' : '/')
const file = '/' + pathname.replace(prefix, '')
fastify.get(pathname, routeOpts, function (req, reply) {
pumpSendToReply(req, reply, file, rootPath)
})
fastify.head(pathname, routeOpts, function (req, reply) {
pumpSendToReply(req, reply, file, rootPath)
})
if (opts.redirect === true) {
fastify.get(pathname.replace(/\/$/, ''), routeOpts, function (req, reply) {
pumpSendToReply(req, reply, file.replace(/\/$/, ''), rootPath)
})
}
fastify.get(pathname, routeOpts, function (req, reply) {
pumpSendToReply(req, reply, file, rootPath)
})
}
if (Array.isArray(sendOptions.root)) {
await Promise.all(sendOptions.root.map(addGlobRoutes))
} else {
await addGlobRoutes(sendOptions.root)
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)
})
}
}

@@ -257,10 +340,14 @@ }

if (Array.isArray(rootPath)) {
if (!rootPath.length) { throw new Error('"root" option array requires one or more paths') }
if (!rootPath.length) {
throw new Error('"root" option array requires one or more paths')
}
if ([...new Set(rootPath)].length !== rootPath.length) {
throw new Error('"root" option array contains one or more duplicate paths')
throw new Error(
'"root" option array contains one or more duplicate paths'
)
}
// check each path and fail at first invalid
rootPath.map(path => checkPath(fastify, path))
rootPath.map((path) => checkPath(fastify, path))
return

@@ -302,2 +389,30 @@ }

const supportedEncodings = ['br', 'gzip', 'deflate']
// Adapted from https://github.com/fastify/fastify-compress/blob/fa5c12a5394285c86d9f438cb39ff44f3d5cde79/index.js#L442
function checkEncodingHeaders (headers, checked) {
if (!('accept-encoding' in headers)) return
let ext
const header = headers['accept-encoding'].toLowerCase().replace('*', 'gzip')
const accepted = encodingNegotiator.negotiate(
header,
supportedEncodings.filter((enc) => !checked.has(enc))
)
switch (accepted) {
case 'br':
ext = 'br'
break
case 'gzip':
if (!checked.has('gz')) {
ext = 'gz'
break
}
}
return ext
}
module.exports = fp(fastifyStatic, {

@@ -304,0 +419,0 @@ fastify: '3.x',

@@ -78,3 +78,3 @@ 'use strict'

htmlInfo: function (entry, route) {
return { href: path.join(path.dirname(route), entry), name: entry }
return { href: path.join(path.dirname(route), entry).replace(/\\/g, '/'), name: entry }
},

@@ -81,0 +81,0 @@

{
"name": "fastify-static",
"version": "4.0.1",
"version": "4.2.0",
"description": "Plugin for serving static files as fast as possible.",

@@ -33,2 +33,3 @@ "main": "index.js",

"content-disposition": "^0.5.3",
"encoding-negotiator": "^2.0.1",
"fastify-plugin": "^3.0.0",

@@ -40,3 +41,3 @@ "glob": "^7.1.4",

"devDependencies": {
"@types/node": "^14.11.2",
"@types/node": "^15.0.0",
"@typescript-eslint/eslint-plugin": "^2.29.0",

@@ -55,4 +56,4 @@ "@typescript-eslint/parser": "^2.29.0",

"standard": "^16.0.2",
"tap": "^14.10.8",
"tsd": "^0.14.0",
"tap": "^15.0.0",
"tsd": "^0.15.0",
"typescript": "^4.0.2"

@@ -59,0 +60,0 @@ },

# fastify-static
![CI workflow](https://github.com/fastify/fastify-static/workflows/CI%20workflow/badge.svg) [![Known Vulnerabilities](https://snyk.io/test/github/fastify/fastify-static/badge.svg)](https://snyk.io/test/github/fastify/fastify-static)
![CI](https://github.com/fastify/fastify-static/workflows/CI/badge.svg)
[![NPM version](https://img.shields.io/npm/v/fastify-static.svg?style=flat)](https://www.npmjs.com/package/fastify-static)
[![Known Vulnerabilities](https://snyk.io/test/github/fastify/fastify-static/badge.svg)](https://snyk.io/test/github/fastify/fastify-static)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
Plugin for serving static files as fast as possible. Supports Fastify version `3.x`.

@@ -146,3 +150,3 @@

If this option is set to `false`, then requesting directories without trailing
If this option is set to `false`, then requesting directories without trailing
slash will trigger your app's 404 handler using `reply.callNotFound()`.

@@ -289,5 +293,23 @@

#### `preCompressed`
Default: `false`
Try to send the brotli encoded asset first (when supported within the `Accept-Encoding` headers), retry for gzip, then the fall back to the original `pathname`. You may choose to skip compression for smaller files that don't benefit from it.
Assume this structure with the compressed asset as a sibling of the un-compressed counterpart:
```
./public
├── main.js
├── main.js.br
├── main.js.gz
├── crit.css
├── crit.css.gz
└── index.html
```
#### Disable serving
If you'd just like to use the reply decorator and not serve whole directories automatically, you can simply pass the option `{ serve: false }`. This will prevent the plugin from serving everything under `root`.
If you would just like to use the reply decorator and not serve whole directories automatically, you can simply pass the option `{ serve: false }`. This will prevent the plugin from serving everything under `root`.

@@ -294,0 +316,0 @@ #### Disabling reply decorator

@@ -17,3 +17,3 @@ 'use strict'

fastify.register(fastifyStatic, options)
t.tearDown(fastify.close.bind(fastify))
t.teardown(fastify.close.bind(fastify))
fastify.listen(0, err => {

@@ -96,4 +96,4 @@ t.error(err)

t.error(err)
t.strictEqual(response.statusCode, 200)
t.strictEqual(body.toString(), JSON.stringify(content))
t.equal(response.statusCode, 200)
t.equal(body.toString(), JSON.stringify(content))
})

@@ -125,4 +125,4 @@ })

t.error(err)
t.strictEqual(response.statusCode, 200)
t.strictEqual(body.toString(), JSON.stringify(content))
t.equal(response.statusCode, 200)
t.equal(body.toString(), JSON.stringify(content))
})

@@ -234,4 +234,4 @@ })

t.error(err)
t.strictEqual(response.statusCode, 200)
t.strictEqual(body.toString(), template.output)
t.equal(response.statusCode, 200)
t.equal(body.toString(), template.output)
})

@@ -269,4 +269,4 @@ })

t.error(err)
t.strictEqual(response.statusCode, 200)
t.strictEqual(body.toString(), JSON.stringify(content))
t.equal(response.statusCode, 200)
t.equal(body.toString(), JSON.stringify(content))
})

@@ -297,4 +297,4 @@ })

t.error(err)
t.strictEqual(response.statusCode, 200)
t.strictEqual(body.toString(), JSON.stringify(content))
t.equal(response.statusCode, 200)
t.equal(body.toString(), JSON.stringify(content))
})

@@ -330,4 +330,4 @@ })

t.error(err)
t.strictEqual(response.statusCode, 200)
t.strictEqual(body.toString(), '<html>\n <body>\n the body\n </body>\n</html>\n')
t.equal(response.statusCode, 200)
t.equal(body.toString(), '<html>\n <body>\n the body\n </body>\n</html>\n')
})

@@ -341,4 +341,4 @@

t.error(err)
t.strictEqual(response.statusCode, 200)
t.strictEqual(body.toString(), 'dir list index')
t.equal(response.statusCode, 200)
t.equal(body.toString(), 'dir list index')
})

@@ -367,3 +367,3 @@ })

t.error(err)
t.strictEqual(response.statusCode, 404)
t.equal(response.statusCode, 404)
})

@@ -394,3 +394,3 @@ })

t.error(err)
t.strictEqual(response.statusCode, 404)
t.equal(response.statusCode, 404)
})

@@ -397,0 +397,0 @@ })

@@ -27,2 +27,3 @@ import fastify from 'fastify'

},
preCompressed: false
}

@@ -29,0 +30,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc