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

fastify-static

Package Overview
Dependencies
Maintainers
14
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.3.0 to 4.4.0

15

index.d.ts

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

import { FastifyPluginCallback, FastifyReply } from 'fastify';
import { Stats } from 'fs';

@@ -19,5 +20,16 @@ declare module "fastify" {

interface ExtendedInformation {
fileCount: number;
totalFileCount: number;
folderCount: number;
totalFolderCount: number;
totalSize: number;
lastModified: number;
}
interface ListDir {
href: string;
name: string;
stats: Stats;
extendedInfo?: ExtendedInformation;
}

@@ -28,2 +40,3 @@

name: string;
stats: Stats;
}

@@ -39,2 +52,4 @@

render: ListRender;
extendedFolderInfo?: boolean;
jsonFormat?: 'names' | 'extended';
}

@@ -41,0 +56,0 @@

13

index.js

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

if (opts.list) {
return dirList.send({
dirList.send({
reply,

@@ -151,3 +151,4 @@ dir: path,

route: pathname
})
}).catch((err) => reply.send(err))
return
}

@@ -166,3 +167,9 @@

if (opts.list && dirList.handle(pathname, opts.list)) {
return dirList.send({ reply, dir: dirList.path(opts.root, pathname), options: opts.list, route: pathname })
dirList.send({
reply,
dir: dirList.path(opts.root, pathname),
options: opts.list,
route: pathname
}).catch((err) => reply.send(err))
return
}

@@ -169,0 +176,0 @@

'use strict'
const path = require('path')
const fs = require('fs')
const fs = require('fs').promises
const pLimit = require('p-limit')

@@ -13,32 +14,80 @@ const dirList = {

*/
list: function (dir, callback) {
list: async function (dir, options) {
const entries = { dirs: [], files: [] }
fs.readdir(dir, (err, files) => {
if (err) {
return callback(err)
}
if (files.length < 1) {
callback(null, entries)
const files = await fs.readdir(dir)
if (files.length < 1) {
return entries
}
const limit = pLimit(4)
await Promise.all(files.map(filename => limit(async () => {
let stats
try {
stats = await fs.stat(path.join(dir, filename))
} catch (error) {
return
}
let j = 0
for (let i = 0; i < files.length; i++) {
const filename = files[i]
fs.stat(path.join(dir, filename), (err, file) => {
if (!err) {
if (file.isDirectory()) {
entries.dirs.push(filename)
} else {
entries.files.push(filename)
}
const entry = { name: filename, stats }
if (stats.isDirectory()) {
if (options.extendedFolderInfo) {
entry.extendedInfo = await getExtendedInfo(path.join(dir, filename))
}
entries.dirs.push(entry)
} else {
entries.files.push(entry)
}
})))
async function getExtendedInfo (folderPath) {
const depth = folderPath.split(path.sep).length
let totalSize = 0
let fileCount = 0
let totalFileCount = 0
let folderCount = 0
let totalFolderCount = 0
let lastModified = 0
async function walk (dir) {
const files = await fs.readdir(dir)
const limit = pLimit(4)
await Promise.all(files.map(filename => limit(async () => {
const filePath = path.join(dir, filename)
let stats
try {
stats = await fs.stat(filePath)
} catch (error) {
return
}
if (j++ >= files.length - 1) {
entries.dirs.sort()
entries.files.sort()
callback(null, entries)
if (stats.isDirectory()) {
totalFolderCount++
if (filePath.split(path.sep).length === depth + 1) {
folderCount++
}
await walk(filePath)
} else {
totalSize += stats.size
totalFileCount++
if (filePath.split(path.sep).length === depth + 1) {
fileCount++
}
lastModified = Math.max(lastModified, stats.mtimeMs)
}
})
})))
}
})
await walk(folderPath)
return {
totalSize,
fileCount,
totalFileCount,
folderCount,
totalFolderCount,
lastModified
}
}
entries.dirs.sort((a, b) => a.name.localeCompare(b.name))
entries.files.sort((a, b) => a.name.localeCompare(b.name))
return entries
},

@@ -53,19 +102,27 @@

*/
send: function ({ reply, dir, options, route }) {
dirList.list(dir, (err, entries) => {
if (err) {
reply.callNotFound()
return
}
send: async function ({ reply, dir, options, route }) {
let entries
try {
entries = await dirList.list(dir, options)
} catch (error) {
return reply.callNotFound()
}
const format = reply.request.query.format || options.format
if (format !== 'html') {
if (options.jsonFormat !== 'extended') {
const nameEntries = { dirs: [], files: [] }
entries.dirs.forEach(entry => nameEntries.dirs.push(entry.name))
entries.files.forEach(entry => nameEntries.files.push(entry.name))
if (options.format !== 'html') {
reply.send(nameEntries)
} else {
reply.send(entries)
return
}
return
}
const html = options.render(
entries.dirs.map(entry => dirList.htmlInfo(entry, route)),
entries.files.map(entry => dirList.htmlInfo(entry, route)))
reply.type('text/html').send(html)
})
const html = options.render(
entries.dirs.map(entry => dirList.htmlInfo(entry, route)),
entries.files.map(entry => dirList.htmlInfo(entry, route)))
reply.type('text/html').send(html)
},

@@ -75,3 +132,3 @@

* provide the html information about entry and route, to get name and full route
* @param {string} entry file or dir name
* @param entry file or dir name and stats
* @param {string} route request route

@@ -81,3 +138,3 @@ * @return {ListFile}

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

@@ -84,0 +141,0 @@

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

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

"glob": "^7.1.4",
"p-limit": "^3.1.0",
"readable-stream": "^3.4.0",

@@ -38,0 +39,0 @@ "send": "^0.17.1"

@@ -225,2 +225,10 @@ # fastify-static

You can override the option with URL parameter `format`. Options are `html` and `json`.
```bash
GET .../public/assets?format=json
```
will return the response as json independent of `list.format`.
**Example:**

@@ -304,2 +312,75 @@

#### `list.extendedFolderInfo`
Default: `undefined`
If `true` some extended information for folders will be accessible in `list.render` and in the json response.
```js
render(dirs, files) {
const dir = dirs[0];
dir.fileCount // number of files in this folder
dir.totalFileCount // number of files in this folder (recursive)
dir.folderCount // number of folders in this folder
dir.totalFolderCount // number of folders in this folder (recursive)
dir.totalSize // size of all files in this folder (recursive)
dir.lastModified // most recent last modified timestamp of all files in this folder (recursive)
}
```
Warning: This will slightly decrease the performance, especially for deeply nested file structures.
#### `list.jsonFormat`
Default: `names`
Options: `names`, `extended`
This option determines the output format when `json` is selected.
`names`:
```json
{
"dirs": [
"dir1",
"dir2"
],
"files": [
"file1.txt",
"file2.txt"
]
}
```
`extended`:
```json
{
"dirs": [
{
"name": "dir1",
"stats": {
"dev": 2100,
"size": 4096,
...
},
"extendedInfo": {
"fileCount": 4,
"totalSize": 51233,
...
}
}
],
"files": [
{
"name": "file1.txt",
"stats": {
"dev": 2200,
"size": 554,
...
}
}
]
}
```
#### `preCompressed`

@@ -306,0 +387,0 @@

@@ -15,4 +15,7 @@ 'use strict'

arrange: function (t, options, f) {
return helper.arrangeModule(t, options, fastifyStatic, f)
},
arrangeModule: function (t, options, mock, f) {
const fastify = Fastify()
fastify.register(fastifyStatic, options)
fastify.register(mock, options)
t.teardown(fastify.close.bind(fastify))

@@ -241,2 +244,82 @@ fastify.listen(0, err => {

t.test('dir list html format - stats', t => {
t.plan(7)
const options1 = {
root: path.join(__dirname, '/static'),
prefix: '/public',
index: false,
list: {
format: 'html',
render (dirs, files) {
t.ok(dirs.length > 0)
t.ok(files.length > 0)
t.ok(dirs.every(every))
t.ok(files.every(every))
function every (value) {
return value.stats &&
value.stats.atime &&
!value.extendedInfo
}
}
}
}
const route = '/public/'
helper.arrange(t, options1, (url) => {
simple.concat({
method: 'GET',
url: url + route
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
})
})
})
t.test('dir list html format - extended info', t => {
t.plan(4)
const route = '/public/'
const options = {
root: path.join(__dirname, '/static'),
prefix: '/public',
index: false,
list: {
format: 'html',
extendedFolderInfo: true,
render (dirs, files) {
t.test('dirs', t => {
t.plan(dirs.length * 7)
for (const value of dirs) {
t.ok(value.extendedInfo)
t.equal(typeof value.extendedInfo.fileCount, 'number')
t.equal(typeof value.extendedInfo.totalFileCount, 'number')
t.equal(typeof value.extendedInfo.folderCount, 'number')
t.equal(typeof value.extendedInfo.totalFolderCount, 'number')
t.equal(typeof value.extendedInfo.totalSize, 'number')
t.equal(typeof value.extendedInfo.lastModified, 'number')
}
})
}
}
}
helper.arrange(t, options, (url) => {
simple.concat({
method: 'GET',
url: url + route
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
})
})
})
t.test('dir list json format', t => {

@@ -275,2 +358,88 @@ t.plan(2)

t.test('dir list json format - extended info', t => {
t.plan(2)
const options = {
root: path.join(__dirname, '/static'),
prefix: '/public',
prefixAvoidTrailingSlash: true,
list: {
format: 'json',
names: ['index', 'index.json', '/'],
extendedFolderInfo: true,
jsonFormat: 'extended'
}
}
const routes = ['/public/shallow/']
helper.arrange(t, options, (url) => {
for (const route of routes) {
t.test(route, t => {
t.plan(5)
simple.concat({
method: 'GET',
url: url + route
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
const bodyObject = JSON.parse(body.toString())
t.equal(bodyObject.dirs[0].name, 'empty')
t.equal(typeof bodyObject.dirs[0].stats.atime, 'string')
t.equal(typeof bodyObject.dirs[0].extendedInfo.totalSize, 'number')
})
})
}
})
})
t.test('dir list - url parameter format', t => {
t.plan(13)
const options = {
root: path.join(__dirname, '/static'),
prefix: '/public',
index: false,
list: {
format: 'html',
render (dirs, files) {
return 'html'
}
}
}
const route = '/public/'
helper.arrange(t, options, (url) => {
simple.concat({
method: 'GET',
url: url + route
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
t.equal(body.toString(), 'html')
t.ok(response.headers['content-type'].includes('text/html'))
})
simple.concat({
method: 'GET',
url: url + route + '?format=html'
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
t.equal(body.toString(), 'html')
t.ok(response.headers['content-type'].includes('text/html'))
})
simple.concat({
method: 'GET',
url: url + route + '?format=json'
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
t.ok(body.toString())
t.ok(response.headers['content-type'].includes('application/json'))
})
})
})
t.test('dir list on empty dir', t => {

@@ -393,1 +562,40 @@ t.plan(2)

})
t.test('dir list error', t => {
t.plan(7)
const options = {
root: path.join(__dirname, '/static'),
prefix: '/public',
prefixAvoidTrailingSlash: true,
index: false,
list: {
format: 'html',
names: ['index', 'index.htm'],
render: () => ''
}
}
const errorMessage = 'mocking send'
const dirList = require('../lib/dirList')
dirList.send = async () => { throw new Error(errorMessage) }
const mock = t.mock('..', {
'../lib/dirList.js': dirList
})
const routes = ['/public/', '/public/index.htm']
helper.arrangeModule(t, options, mock, (url) => {
for (const route of routes) {
simple.concat({
method: 'GET',
url: url + route
}, (err, response, body) => {
t.error(err)
t.equal(JSON.parse(body.toString()).message, errorMessage)
t.equal(response.statusCode, 500)
})
}
})
})
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