@fastify/static 6.0.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  ![CI](https://github.com/fastify/fastify-static/workflows/CI/badge.svg)
4
4
  [![NPM version](https://img.shields.io/npm/v/@fastify/static.svg?style=flat)](https://www.npmjs.com/package/@fastify/static)
5
- [![Known Vulnerabilities](https://snyk.io/test/github/fastify/fastify-static/badge.svg)](https://snyk.io/test/github/fastify/fastify-static)
6
5
  [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
7
6
 
8
7
  Plugin for serving static files as fast as possible. Supports Fastify version `3.x`.
@@ -191,7 +190,9 @@ If set, it provide the directory list calling the directory path.
191
190
 
192
191
  Default response is json.
193
192
 
194
- Note: Multi-root is not supported within the `list` option.
193
+ Note:
194
+ - Multi-root is not supported within the `list` option.
195
+ - If `dotfiles` option value is `deny` or `ignore`, dotfiles will be excluded.
195
196
 
196
197
  **Example:**
197
198
 
package/index.d.ts CHANGED
@@ -43,13 +43,20 @@ interface ListRender {
43
43
  }
44
44
 
45
45
  interface ListOptions {
46
- format: 'json' | 'html';
47
- names: string[];
48
- render: ListRender;
46
+ names?: string[];
49
47
  extendedFolderInfo?: boolean;
48
+ }
49
+
50
+ interface ListOptionsJsonFormat extends ListOptions {
51
+ format: 'json';
50
52
  jsonFormat?: 'names' | 'extended';
51
53
  }
52
54
 
55
+ interface ListOptionsHtmlFormat extends ListOptions {
56
+ format: 'html';
57
+ render: ListRender;
58
+ }
59
+
53
60
  // Passed on to `send`
54
61
  interface SendOptions {
55
62
  acceptRanges?: boolean;
@@ -73,7 +80,7 @@ export interface FastifyStaticOptions extends SendOptions {
73
80
  setHeaders?: (...args: any[]) => void;
74
81
  redirect?: boolean;
75
82
  wildcard?: boolean;
76
- list?: boolean | ListOptions;
83
+ list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
77
84
  allowedPath?: (pathName: string, root?: string) => boolean;
78
85
  /**
79
86
  * @description
package/index.js CHANGED
@@ -161,7 +161,8 @@ async function fastifyStatic (fastify, opts) {
161
161
  dir: path,
162
162
  options: opts.list,
163
163
  route: pathname,
164
- prefix
164
+ prefix,
165
+ dotfiles: opts.dotfiles
165
166
  }).catch((err) => reply.send(err))
166
167
  return
167
168
  }
@@ -217,7 +218,8 @@ async function fastifyStatic (fastify, opts) {
217
218
  dir: dirList.path(opts.root, pathname),
218
219
  options: opts.list,
219
220
  route: pathname,
220
- prefix
221
+ prefix,
222
+ dotfiles: opts.dotfiles
221
223
  }).catch((err) => reply.send(err))
222
224
  return
223
225
  }
@@ -331,8 +333,10 @@ async function fastifyStatic (fastify, opts) {
331
333
  const indexDirs = new Map()
332
334
  const routes = new Set()
333
335
 
336
+ const winSeparatorRegex = new RegExp(`\\${path.win32.sep}`, 'g')
337
+
334
338
  for (const rootPath of Array.isArray(sendOptions.root) ? sendOptions.root : [sendOptions.root]) {
335
- const files = await globPromise(path.join(rootPath, globPattern), { nodir: true })
339
+ const files = await globPromise(path.join(rootPath, globPattern).replace(winSeparatorRegex, path.posix.sep), { nodir: true })
336
340
  const indexes = typeof opts.index === 'undefined' ? ['index.html'] : [].concat(opts.index)
337
341
 
338
342
  for (let file of files) {
package/lib/dirList.js CHANGED
@@ -8,12 +8,16 @@ const dirList = {
8
8
  /**
9
9
  * get files and dirs from dir, or error
10
10
  * @param {string} dir full path fs dir
11
- * @param {function(error, entries)} callback
11
+ * @param {(boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat)} options
12
+ * @param {string} dotfiles
12
13
  * note: can't use glob because don't get error on non existing dir
13
14
  */
14
- list: async function (dir, options) {
15
+ list: async function (dir, options, dotfiles) {
15
16
  const entries = { dirs: [], files: [] }
16
- const files = await fs.readdir(dir)
17
+ let files = await fs.readdir(dir)
18
+ if (dotfiles === 'deny' || dotfiles === 'ignore') {
19
+ files = files.filter(f => f.charAt(0) !== '.')
20
+ }
17
21
  if (files.length < 1) {
18
22
  return entries
19
23
  }
@@ -95,13 +99,14 @@ const dirList = {
95
99
  * send dir list content, or 404 on error
96
100
  * @param {Fastify.Reply} reply
97
101
  * @param {string} dir full path fs dir
98
- * @param {ListOptions} options
102
+ * @param {(boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat)} options
99
103
  * @param {string} route request route
104
+ * @param {string} dotfiles
100
105
  */
101
- send: async function ({ reply, dir, options, route, prefix }) {
106
+ send: async function ({ reply, dir, options, route, prefix, dotfiles }) {
102
107
  let entries
103
108
  try {
104
- entries = await dirList.list(dir, options)
109
+ entries = await dirList.list(dir, options, dotfiles)
105
110
  } catch (error) {
106
111
  return reply.callNotFound()
107
112
  }
@@ -146,7 +151,7 @@ const dirList = {
146
151
  /**
147
152
  * say if the route can be handled by dir list or not
148
153
  * @param {string} route request route
149
- * @param {ListOptions} options
154
+ * @param {(boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat)} options
150
155
  * @return {boolean}
151
156
  */
152
157
  handle: function (route, options) {
@@ -189,9 +194,15 @@ const dirList = {
189
194
  if (options.list.names && !Array.isArray(options.list.names)) {
190
195
  return new TypeError('The `list.names` option must be an array')
191
196
  }
197
+ if (options.list.jsonFormat != null && options.list.jsonFormat !== 'names' && options.list.jsonFormat !== 'extended') {
198
+ return new TypeError('The `list.jsonFormat` option must be name or extended')
199
+ }
192
200
  if (options.list.format === 'html' && typeof options.list.render !== 'function') {
193
201
  return new TypeError('The `list.render` option must be a function and is required with html format')
194
202
  }
203
+ if (options.list.format === 'html' && options.list.jsonFormat != null) {
204
+ return new TypeError('The `list.jsonFormat` option must be with json format')
205
+ }
195
206
  }
196
207
 
197
208
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/static",
3
- "version": "6.0.0",
3
+ "version": "6.3.0",
4
4
  "description": "Plugin for serving static files as fast as possible.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "content-disposition": "^0.5.3",
33
33
  "encoding-negotiator": "^2.0.1",
34
34
  "fastify-plugin": "^3.0.0",
35
- "glob": "^7.1.4",
35
+ "glob": "^8.0.1",
36
36
  "p-limit": "^3.1.0",
37
37
  "readable-stream": "^3.4.0",
38
38
  "send": "^0.18.0"
@@ -40,7 +40,7 @@ t.test('throws when `root` is an array', t => {
40
40
  t.equal(err.message, 'multi-root with list option is not supported')
41
41
  })
42
42
 
43
- t.test('throws when `list.format option` is invalid', t => {
43
+ t.test('throws when `list.format` option is invalid', t => {
44
44
  t.plan(2)
45
45
 
46
46
  const err = dirList.validateOptions({ list: { format: 'hello' } })
@@ -56,6 +56,14 @@ t.test('throws when `list.names option` is not an array', t => {
56
56
  t.equal(err.message, 'The `list.names` option must be an array')
57
57
  })
58
58
 
59
+ t.test('throws when `list.jsonFormat` option is invalid', t => {
60
+ t.plan(2)
61
+
62
+ const err = dirList.validateOptions({ list: { jsonFormat: 'hello' } })
63
+ t.type(err, TypeError)
64
+ t.equal(err.message, 'The `list.jsonFormat` option must be name or extended')
65
+ })
66
+
59
67
  t.test('throws when `list.format` is html and `list render` is not a function', t => {
60
68
  t.plan(2)
61
69
 
@@ -64,6 +72,14 @@ t.test('throws when `list.format` is html and `list render` is not a function',
64
72
  t.equal(err.message, 'The `list.render` option must be a function and is required with html format')
65
73
  })
66
74
 
75
+ t.test('throws when `list.format` is html and `list.jsonFormat` is given', t => {
76
+ t.plan(2)
77
+
78
+ const err = dirList.validateOptions({ list: { format: 'html', render: () => '', jsonFormat: 'extended' } })
79
+ t.type(err, TypeError)
80
+ t.equal(err.message, 'The `list.jsonFormat` option must be with json format')
81
+ })
82
+
67
83
  t.test('dir list wrong options', t => {
68
84
  t.plan(3)
69
85
 
@@ -636,6 +652,90 @@ t.test('serve a non existent dir and get error', t => {
636
652
  })
637
653
  })
638
654
 
655
+ t.test('dir list with dotfiles allow option', t => {
656
+ t.plan(2)
657
+
658
+ const options = {
659
+ root: path.join(__dirname, '/static-dotfiles'),
660
+ prefix: '/public',
661
+ dotfiles: 'allow',
662
+ index: false,
663
+ list: true
664
+ }
665
+ const route = '/public/'
666
+ const content = { dirs: ['dir'], files: ['.aaa', 'test.txt'] }
667
+
668
+ helper.arrange(t, options, (url) => {
669
+ t.test(route, t => {
670
+ t.plan(3)
671
+ simple.concat({
672
+ method: 'GET',
673
+ url: url + route
674
+ }, (err, response, body) => {
675
+ t.error(err)
676
+ t.equal(response.statusCode, 200)
677
+ t.equal(body.toString(), JSON.stringify(content))
678
+ })
679
+ })
680
+ })
681
+ })
682
+
683
+ t.test('dir list with dotfiles deny option', t => {
684
+ t.plan(2)
685
+
686
+ const options = {
687
+ root: path.join(__dirname, '/static-dotfiles'),
688
+ prefix: '/public',
689
+ dotfiles: 'deny',
690
+ index: false,
691
+ list: true
692
+ }
693
+ const route = '/public/'
694
+ const content = { dirs: ['dir'], files: ['test.txt'] }
695
+
696
+ helper.arrange(t, options, (url) => {
697
+ t.test(route, t => {
698
+ t.plan(3)
699
+ simple.concat({
700
+ method: 'GET',
701
+ url: url + route
702
+ }, (err, response, body) => {
703
+ t.error(err)
704
+ t.equal(response.statusCode, 200)
705
+ t.equal(body.toString(), JSON.stringify(content))
706
+ })
707
+ })
708
+ })
709
+ })
710
+
711
+ t.test('dir list with dotfiles ignore option', t => {
712
+ t.plan(2)
713
+
714
+ const options = {
715
+ root: path.join(__dirname, '/static-dotfiles'),
716
+ prefix: '/public',
717
+ dotfiles: 'ignore',
718
+ index: false,
719
+ list: true
720
+ }
721
+ const route = '/public/'
722
+ const content = { dirs: ['dir'], files: ['test.txt'] }
723
+
724
+ helper.arrange(t, options, (url) => {
725
+ t.test(route, t => {
726
+ t.plan(3)
727
+ simple.concat({
728
+ method: 'GET',
729
+ url: url + route
730
+ }, (err, response, body) => {
731
+ t.error(err)
732
+ t.equal(response.statusCode, 200)
733
+ t.equal(body.toString(), JSON.stringify(content))
734
+ })
735
+ })
736
+ })
737
+ })
738
+
639
739
  t.test('dir list error', t => {
640
740
  t.plan(7)
641
741
 
File without changes
File without changes
File without changes
@@ -1,5 +1,5 @@
1
1
  import fastify from 'fastify'
2
- import { expectError } from 'tsd'
2
+ import { expectAssignable, expectError } from 'tsd'
3
3
  import fastifyStatic, { FastifyStaticOptions } from '../..'
4
4
 
5
5
  const appWithImplicitHttp = fastify()
@@ -32,6 +32,36 @@ expectError<FastifyStaticOptions>({
32
32
  wildcard: '**/**'
33
33
  })
34
34
 
35
+ expectAssignable<FastifyStaticOptions>({
36
+ root: '',
37
+ list: {
38
+ format: 'json',
39
+ }
40
+ })
41
+
42
+ expectError<FastifyStaticOptions>({
43
+ root: '',
44
+ list: {
45
+ format: 'json',
46
+ render: () => ''
47
+ }
48
+ })
49
+
50
+ expectAssignable<FastifyStaticOptions>({
51
+ root: '',
52
+ list: {
53
+ format: 'html',
54
+ render: () => ''
55
+ }
56
+ })
57
+
58
+ expectError<FastifyStaticOptions>({
59
+ root: '',
60
+ list: {
61
+ format: 'html',
62
+ }
63
+ })
64
+
35
65
  appWithImplicitHttp
36
66
  .register(fastifyStatic, options)
37
67
  .after(() => {