@fastify/static 6.10.2 → 6.11.1
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/.eslintrc.json +51 -0
- package/.taprc +2 -0
- package/README.md +22 -3
- package/example/server-compress.js +1 -1
- package/example/server-dir-list.js +1 -1
- package/example/server-hidden-file.js +1 -1
- package/example/server.js +1 -1
- package/index.js +146 -142
- package/lib/dirList.js +7 -9
- package/package.json +17 -16
- package/test/content-type.test.js +1 -1
- package/test/dir-list.test.js +2 -2
- package/test/static-encode/[...]/a .md +1 -0
- package/test/static.test.js +42 -5
- package/tsconfig.eslint.json +13 -0
- package/types/index.d.ts +6 -6
- package/types/index.test-d.ts +50 -28
- package/test/content-type/sample.jpg.br +0 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"eslint:recommended",
|
|
4
|
+
"plugin:@typescript-eslint/eslint-recommended",
|
|
5
|
+
"plugin:@typescript-eslint/recommended",
|
|
6
|
+
"standard"
|
|
7
|
+
],
|
|
8
|
+
"parser": "@typescript-eslint/parser",
|
|
9
|
+
"plugins": ["@typescript-eslint"],
|
|
10
|
+
"env": { "node": true },
|
|
11
|
+
"parserOptions": {
|
|
12
|
+
"ecmaVersion": 6,
|
|
13
|
+
"sourceType": "module",
|
|
14
|
+
"project": "./tsconfig.eslint.json",
|
|
15
|
+
"createDefaultProgram": true
|
|
16
|
+
},
|
|
17
|
+
"rules": {
|
|
18
|
+
"no-console": "off",
|
|
19
|
+
"@typescript-eslint/indent": ["error", 2],
|
|
20
|
+
"semi": ["error", "never"],
|
|
21
|
+
"import/export": "off" // this errors on multiple exports (overload interfaces)
|
|
22
|
+
},
|
|
23
|
+
"overrides": [
|
|
24
|
+
{
|
|
25
|
+
"files": ["*.d.ts","*.test-d.ts"],
|
|
26
|
+
"rules": {
|
|
27
|
+
"no-use-before-define": "off",
|
|
28
|
+
"no-redeclare": "off",
|
|
29
|
+
"@typescript-eslint/no-explicit-any": "off"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"files": ["*.test-d.ts"],
|
|
34
|
+
"rules": {
|
|
35
|
+
"@typescript-eslint/no-var-requires": "off",
|
|
36
|
+
"no-unused-vars": "off",
|
|
37
|
+
"n/handle-callback-err": "off",
|
|
38
|
+
"@typescript-eslint/no-empty-function": "off",
|
|
39
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
40
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
41
|
+
"@typescript-eslint/no-non-null-assertion": "off",
|
|
42
|
+
"@typescript-eslint/no-misused-promises": ["error", {
|
|
43
|
+
"checksVoidReturn": false
|
|
44
|
+
}]
|
|
45
|
+
},
|
|
46
|
+
"globals": {
|
|
47
|
+
"NodeJS": "readonly"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
package/.taprc
ADDED
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Plugin for serving static files as fast as possible. Supports Fastify version `4
|
|
|
21
21
|
|
|
22
22
|
```js
|
|
23
23
|
const fastify = require('fastify')({logger: true})
|
|
24
|
-
const path = require('path')
|
|
24
|
+
const path = require('node:path')
|
|
25
25
|
|
|
26
26
|
fastify.register(require('@fastify/static'), {
|
|
27
27
|
root: path.join(__dirname, 'public'),
|
|
@@ -57,7 +57,7 @@ fastify.listen({ port: 3000 }, (err, address) => {
|
|
|
57
57
|
```js
|
|
58
58
|
const fastify = require('fastify')()
|
|
59
59
|
const fastifyStatic = require('@fastify/static')
|
|
60
|
-
const path = require('path')
|
|
60
|
+
const path = require('node:path')
|
|
61
61
|
// first plugin
|
|
62
62
|
fastify.register(fastifyStatic, {
|
|
63
63
|
root: path.join(__dirname, 'public')
|
|
@@ -76,7 +76,7 @@ fastify.register(fastifyStatic, {
|
|
|
76
76
|
|
|
77
77
|
```js
|
|
78
78
|
const fastify = require('fastify')()
|
|
79
|
-
const path = require('path')
|
|
79
|
+
const path = require('node:path')
|
|
80
80
|
|
|
81
81
|
fastify.register(require('@fastify/static'), {
|
|
82
82
|
root: path.join(__dirname, 'public'),
|
|
@@ -452,6 +452,25 @@ If a request matches the URL `prefix` but a file cannot be found for the
|
|
|
452
452
|
request, Fastify's 404 handler will be called. You can set a custom 404
|
|
453
453
|
handler with [`fastify.setNotFoundHandler()`](https://www.fastify.io/docs/latest/Reference/Server/#setnotfoundhandler).
|
|
454
454
|
|
|
455
|
+
When registering `@fastify/static` within an encapsulated context, the `wildcard` option may need to be set to `false` in order to support index resolution and nested not-found-handler:
|
|
456
|
+
|
|
457
|
+
```js
|
|
458
|
+
const app = require('fastify')();
|
|
459
|
+
|
|
460
|
+
app.register((childContext, _, done) => {
|
|
461
|
+
childContext.register(require('@fastify/static'), {
|
|
462
|
+
root: path.join(__dirname, 'docs'), // docs is a folder that contains `index.html` and `404.html`
|
|
463
|
+
wildcard: false
|
|
464
|
+
});
|
|
465
|
+
childContext.setNotFoundHandler((_, reply) => {
|
|
466
|
+
return reply.code(404).type('text/html').sendFile('404.html');
|
|
467
|
+
});
|
|
468
|
+
done();
|
|
469
|
+
}, { prefix: 'docs' });
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
This code will send the `index.html` for the paths `docs`, `docs/`, and `docs/index.html`. For all other `docs/<undefined-routes>` it will reply with `404.html`.
|
|
473
|
+
|
|
455
474
|
### Handling Errors
|
|
456
475
|
|
|
457
476
|
If an error occurs while trying to send a file, the error will be passed
|
package/example/server.js
CHANGED
package/index.js
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
3
|
+
const { PassThrough } = require('node:stream')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
const { fileURLToPath } = require('node:url')
|
|
6
|
+
const { statSync } = require('node:fs')
|
|
7
|
+
const { promisify } = require('node:util')
|
|
7
8
|
const glob = require('glob')
|
|
8
|
-
const
|
|
9
|
-
const contentDisposition = require('content-disposition')
|
|
9
|
+
const globPromise = promisify(glob)
|
|
10
10
|
const fp = require('fastify-plugin')
|
|
11
|
-
const
|
|
12
|
-
const globPromise = util.promisify(glob)
|
|
11
|
+
const send = require('@fastify/send')
|
|
13
12
|
const encodingNegotiator = require('@fastify/accept-negotiator')
|
|
14
|
-
|
|
15
|
-
send.mime.default_type = 'application/octet-stream'
|
|
13
|
+
const contentDisposition = require('content-disposition')
|
|
16
14
|
|
|
17
15
|
const dirList = require('./lib/dirList')
|
|
18
16
|
|
|
17
|
+
const winSeparatorRegex = new RegExp(`\\${path.win32.sep}`, 'g')
|
|
18
|
+
const backslashRegex = /\\/g
|
|
19
|
+
const startForwardSlashRegex = /^\//
|
|
20
|
+
const endForwardSlashRegex = /\/$/
|
|
21
|
+
const doubleForwardSlashRegex = /\/\//g
|
|
22
|
+
const asteriskRegex = /\*/g
|
|
23
|
+
|
|
24
|
+
const supportedEncodings = ['br', 'gzip', 'deflate']
|
|
25
|
+
send.mime.default_type = 'application/octet-stream'
|
|
26
|
+
|
|
19
27
|
async function fastifyStatic (fastify, opts) {
|
|
20
28
|
opts.root = normalizeRoot(opts.root)
|
|
21
29
|
checkRootPathForErrors(fastify, opts.root)
|
|
22
30
|
|
|
23
31
|
const setHeaders = opts.setHeaders
|
|
24
|
-
|
|
25
32
|
if (setHeaders !== undefined && typeof setHeaders !== 'function') {
|
|
26
33
|
throw new TypeError('The `setHeaders` option must be a function')
|
|
27
34
|
}
|
|
@@ -48,19 +55,124 @@ async function fastifyStatic (fastify, opts) {
|
|
|
48
55
|
maxAge: opts.maxAge
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (opts.prefix === undefined) opts.prefix = '/'
|
|
54
|
-
|
|
55
|
-
let prefix = opts.prefix
|
|
58
|
+
let prefix = opts.prefix ?? (opts.prefix = '/')
|
|
56
59
|
|
|
57
60
|
if (!opts.prefixAvoidTrailingSlash) {
|
|
58
61
|
prefix =
|
|
59
|
-
|
|
60
|
-
?
|
|
61
|
-
:
|
|
62
|
+
prefix[prefix.length - 1] === '/'
|
|
63
|
+
? prefix
|
|
64
|
+
: prefix + '/'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Set the schema hide property if defined in opts or true by default
|
|
68
|
+
const routeOpts = {
|
|
69
|
+
constraints: opts.constraints,
|
|
70
|
+
schema: {
|
|
71
|
+
hide: opts.schemaHide !== undefined ? opts.schemaHide : true
|
|
72
|
+
},
|
|
73
|
+
errorHandler (error, request, reply) {
|
|
74
|
+
if (error?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
|
|
75
|
+
reply.request.raw.destroy()
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fastify.errorHandler(error, request, reply)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (opts.decorateReply !== false) {
|
|
84
|
+
fastify.decorateReply('sendFile', function (filePath, rootPath, options) {
|
|
85
|
+
const opts = typeof rootPath === 'object' ? rootPath : options
|
|
86
|
+
const root = typeof rootPath === 'string' ? rootPath : opts && opts.root
|
|
87
|
+
pumpSendToReply(
|
|
88
|
+
this.request,
|
|
89
|
+
this,
|
|
90
|
+
filePath,
|
|
91
|
+
root || sendOptions.root,
|
|
92
|
+
0,
|
|
93
|
+
opts
|
|
94
|
+
)
|
|
95
|
+
return this
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
fastify.decorateReply(
|
|
99
|
+
'download',
|
|
100
|
+
function (filePath, fileName, options = {}) {
|
|
101
|
+
const { root, ...opts } =
|
|
102
|
+
typeof fileName === 'object' ? fileName : options
|
|
103
|
+
fileName = typeof fileName === 'string' ? fileName : filePath
|
|
104
|
+
|
|
105
|
+
// Set content disposition header
|
|
106
|
+
this.header('content-disposition', contentDisposition(fileName))
|
|
107
|
+
|
|
108
|
+
pumpSendToReply(this.request, this, filePath, root, 0, opts)
|
|
109
|
+
|
|
110
|
+
return this
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (opts.serve !== false) {
|
|
116
|
+
if (opts.wildcard && typeof opts.wildcard !== 'boolean') {
|
|
117
|
+
throw new Error('"wildcard" option must be a boolean')
|
|
118
|
+
}
|
|
119
|
+
if (opts.wildcard === undefined || opts.wildcard === true) {
|
|
120
|
+
fastify.head(prefix + '*', routeOpts, function (req, reply) {
|
|
121
|
+
pumpSendToReply(req, reply, '/' + req.params['*'], sendOptions.root)
|
|
122
|
+
})
|
|
123
|
+
fastify.get(prefix + '*', routeOpts, function (req, reply) {
|
|
124
|
+
pumpSendToReply(req, reply, '/' + req.params['*'], sendOptions.root)
|
|
125
|
+
})
|
|
126
|
+
if (opts.redirect === true && prefix !== opts.prefix) {
|
|
127
|
+
fastify.get(opts.prefix, routeOpts, function (req, reply) {
|
|
128
|
+
reply.redirect(301, getRedirectUrl(req.raw.url))
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
const globPattern = '**/**'
|
|
133
|
+
const indexDirs = new Map()
|
|
134
|
+
const routes = new Set()
|
|
135
|
+
|
|
136
|
+
const roots = Array.isArray(sendOptions.root) ? sendOptions.root : [sendOptions.root]
|
|
137
|
+
for (let i = 0; i < roots.length; ++i) {
|
|
138
|
+
const rootPath = roots[i]
|
|
139
|
+
const files = await globPromise(path.join(rootPath, globPattern).replace(winSeparatorRegex, path.posix.sep), { nodir: true, dot: opts.serveDotFiles })
|
|
140
|
+
const indexes = opts.index === undefined ? ['index.html'] : [].concat(opts.index)
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < files.length; ++i) {
|
|
143
|
+
const file = files[i].replace(rootPath.replace(backslashRegex, '/'), '')
|
|
144
|
+
.replace(startForwardSlashRegex, '')
|
|
145
|
+
const route = (prefix + file).replace(doubleForwardSlashRegex, '/')
|
|
146
|
+
|
|
147
|
+
if (routes.has(route)) {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
routes.add(route)
|
|
152
|
+
|
|
153
|
+
setUpHeadAndGet(routeOpts, route, '/' + file, rootPath)
|
|
154
|
+
|
|
155
|
+
const key = path.posix.basename(route)
|
|
156
|
+
if (indexes.includes(key) && !indexDirs.has(key)) {
|
|
157
|
+
indexDirs.set(path.posix.dirname(route), rootPath)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const [dirname, rootPath] of indexDirs.entries()) {
|
|
163
|
+
const pathname = dirname + (dirname.endsWith('/') ? '' : '/')
|
|
164
|
+
const file = '/' + pathname.replace(prefix, '')
|
|
165
|
+
setUpHeadAndGet(routeOpts, pathname, file, rootPath)
|
|
166
|
+
|
|
167
|
+
if (opts.redirect === true) {
|
|
168
|
+
setUpHeadAndGet(routeOpts, pathname.replace(endForwardSlashRegex, ''), file.replace(endForwardSlashRegex, ''), rootPath)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
62
172
|
}
|
|
63
173
|
|
|
174
|
+
const allowedPath = opts.allowedPath
|
|
175
|
+
|
|
64
176
|
function pumpSendToReply (
|
|
65
177
|
request,
|
|
66
178
|
reply,
|
|
@@ -271,121 +383,12 @@ async function fastifyStatic (fastify, opts) {
|
|
|
271
383
|
stream.pipe(wrap)
|
|
272
384
|
}
|
|
273
385
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
reply.request.raw.destroy()
|
|
277
|
-
return
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
fastify.errorHandler(error, request, reply)
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Set the schema hide property if defined in opts or true by default
|
|
284
|
-
const routeOpts = {
|
|
285
|
-
constraints: opts.constraints,
|
|
286
|
-
schema: {
|
|
287
|
-
hide: typeof opts.schemaHide !== 'undefined' ? opts.schemaHide : true
|
|
288
|
-
},
|
|
289
|
-
errorHandler
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (opts.decorateReply !== false) {
|
|
293
|
-
fastify.decorateReply('sendFile', function (filePath, rootPath, options) {
|
|
294
|
-
const opts = typeof rootPath === 'object' ? rootPath : options
|
|
295
|
-
const root = typeof rootPath === 'string' ? rootPath : opts && opts.root
|
|
296
|
-
pumpSendToReply(
|
|
297
|
-
this.request,
|
|
298
|
-
this,
|
|
299
|
-
filePath,
|
|
300
|
-
root || sendOptions.root,
|
|
301
|
-
0,
|
|
302
|
-
opts
|
|
303
|
-
)
|
|
304
|
-
return this
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
fastify.decorateReply(
|
|
308
|
-
'download',
|
|
309
|
-
function (filePath, fileName, options = {}) {
|
|
310
|
-
const { root, ...opts } =
|
|
311
|
-
typeof fileName === 'object' ? fileName : options
|
|
312
|
-
fileName = typeof fileName === 'string' ? fileName : filePath
|
|
313
|
-
|
|
314
|
-
// Set content disposition header
|
|
315
|
-
this.header('content-disposition', contentDisposition(fileName))
|
|
316
|
-
|
|
317
|
-
pumpSendToReply(this.request, this, filePath, root, 0, opts)
|
|
318
|
-
|
|
319
|
-
return this
|
|
320
|
-
}
|
|
321
|
-
)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (opts.serve !== false) {
|
|
325
|
-
if (opts.wildcard && typeof opts.wildcard !== 'boolean') {
|
|
326
|
-
throw new Error('"wildcard" option must be a boolean')
|
|
327
|
-
}
|
|
328
|
-
if (opts.wildcard === undefined || opts.wildcard === true) {
|
|
329
|
-
fastify.head(prefix + '*', routeOpts, function (req, reply) {
|
|
330
|
-
pumpSendToReply(req, reply, '/' + req.params['*'], sendOptions.root)
|
|
331
|
-
})
|
|
332
|
-
fastify.get(prefix + '*', routeOpts, function (req, reply) {
|
|
333
|
-
pumpSendToReply(req, reply, '/' + req.params['*'], sendOptions.root)
|
|
334
|
-
})
|
|
335
|
-
if (opts.redirect === true && prefix !== opts.prefix) {
|
|
336
|
-
fastify.get(opts.prefix, routeOpts, function (req, reply) {
|
|
337
|
-
reply.redirect(301, getRedirectUrl(req.raw.url))
|
|
338
|
-
})
|
|
339
|
-
}
|
|
340
|
-
} else {
|
|
341
|
-
const globPattern = '**/**'
|
|
342
|
-
const indexDirs = new Map()
|
|
343
|
-
const routes = new Set()
|
|
344
|
-
|
|
345
|
-
const winSeparatorRegex = new RegExp(`\\${path.win32.sep}`, 'g')
|
|
346
|
-
|
|
347
|
-
for (const rootPath of Array.isArray(sendOptions.root) ? sendOptions.root : [sendOptions.root]) {
|
|
348
|
-
const files = await globPromise(path.join(rootPath, globPattern).replace(winSeparatorRegex, path.posix.sep), { nodir: true, dot: opts.serveDotFiles })
|
|
349
|
-
const indexes = typeof opts.index === 'undefined' ? ['index.html'] : [].concat(opts.index)
|
|
350
|
-
|
|
351
|
-
for (let file of files) {
|
|
352
|
-
file = file
|
|
353
|
-
.replace(rootPath.replace(/\\/g, '/'), '')
|
|
354
|
-
.replace(/^\//, '')
|
|
355
|
-
const route = (prefix + file).replace(/\/\//g, '/')
|
|
356
|
-
if (routes.has(route)) {
|
|
357
|
-
continue
|
|
358
|
-
}
|
|
359
|
-
routes.add(route)
|
|
360
|
-
|
|
361
|
-
setUpHeadAndGet(fastify, routeOpts, route, '/' + file, rootPath)
|
|
362
|
-
|
|
363
|
-
const key = path.posix.basename(route)
|
|
364
|
-
if (indexes.includes(key) && !indexDirs.has(key)) {
|
|
365
|
-
indexDirs.set(path.posix.dirname(route), rootPath)
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
for (const [dirname, rootPath] of indexDirs.entries()) {
|
|
371
|
-
const pathname = dirname + (dirname.endsWith('/') ? '' : '/')
|
|
372
|
-
const file = '/' + pathname.replace(prefix, '')
|
|
373
|
-
setUpHeadAndGet(fastify, routeOpts, pathname, file, rootPath)
|
|
374
|
-
|
|
375
|
-
if (opts.redirect === true) {
|
|
376
|
-
setUpHeadAndGet(fastify, routeOpts, pathname.replace(/\/$/, ''), file.replace(/\/$/, ''), rootPath)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function setUpHeadAndGet (fastify, routeOpts, route, file, rootPath) {
|
|
383
|
-
const toSetUp = {
|
|
384
|
-
...routeOpts,
|
|
386
|
+
function setUpHeadAndGet (routeOpts, route, file, rootPath) {
|
|
387
|
+
const toSetUp = Object.assign({}, routeOpts, {
|
|
385
388
|
method: ['HEAD', 'GET'],
|
|
386
389
|
url: route,
|
|
387
390
|
handler: serveFileHandler
|
|
388
|
-
}
|
|
391
|
+
})
|
|
389
392
|
toSetUp.config = toSetUp.config || {}
|
|
390
393
|
toSetUp.config.file = file
|
|
391
394
|
toSetUp.config.rootPath = rootPath
|
|
@@ -393,23 +396,23 @@ async function fastifyStatic (fastify, opts) {
|
|
|
393
396
|
}
|
|
394
397
|
|
|
395
398
|
function serveFileHandler (req, reply) {
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
pumpSendToReply(req, reply, file, rootPath)
|
|
399
|
+
const routeConfig = req.routeOptions.config
|
|
400
|
+
pumpSendToReply(req, reply, routeConfig.file, routeConfig.rootPath)
|
|
399
401
|
}
|
|
400
402
|
}
|
|
403
|
+
|
|
401
404
|
function normalizeRoot (root) {
|
|
402
405
|
if (root === undefined) {
|
|
403
406
|
return root
|
|
404
407
|
}
|
|
405
408
|
if (root instanceof URL && root.protocol === 'file:') {
|
|
406
|
-
return
|
|
409
|
+
return fileURLToPath(root)
|
|
407
410
|
}
|
|
408
411
|
if (Array.isArray(root)) {
|
|
409
412
|
const result = []
|
|
410
413
|
for (let i = 0, il = root.length; i < il; ++i) {
|
|
411
414
|
if (root[i] instanceof URL && root[i].protocol === 'file:') {
|
|
412
|
-
result.push(
|
|
415
|
+
result.push(fileURLToPath(root[i]))
|
|
413
416
|
} else {
|
|
414
417
|
result.push(root[i])
|
|
415
418
|
}
|
|
@@ -475,8 +478,6 @@ function checkPath (fastify, rootPath) {
|
|
|
475
478
|
}
|
|
476
479
|
}
|
|
477
480
|
|
|
478
|
-
const supportedEncodings = ['br', 'gzip', 'deflate']
|
|
479
|
-
|
|
480
481
|
function getContentType (path) {
|
|
481
482
|
const type = send.mime.getType(path) || send.mime.default_type
|
|
482
483
|
|
|
@@ -508,7 +509,9 @@ function findIndexFile (pathname, root, indexFiles = ['index.html']) {
|
|
|
508
509
|
function getEncodingHeader (headers, checked) {
|
|
509
510
|
if (!('accept-encoding' in headers)) return
|
|
510
511
|
|
|
511
|
-
|
|
512
|
+
// consider the no-preference token as gzip for downstream compat
|
|
513
|
+
const header = headers['accept-encoding'].toLowerCase().replace(asteriskRegex, 'gzip')
|
|
514
|
+
|
|
512
515
|
return encodingNegotiator.negotiate(
|
|
513
516
|
header,
|
|
514
517
|
supportedEncodings.filter((enc) => !checked.has(enc))
|
|
@@ -528,14 +531,15 @@ function getEncodingExtension (encoding) {
|
|
|
528
531
|
function getRedirectUrl (url) {
|
|
529
532
|
let i = 0
|
|
530
533
|
// we detect how many slash before a valid path
|
|
531
|
-
for (
|
|
534
|
+
for (; i < url.length; ++i) {
|
|
532
535
|
if (url[i] !== '/' && url[i] !== '\\') break
|
|
533
536
|
}
|
|
534
537
|
// turns all leading / or \ into a single /
|
|
535
538
|
url = '/' + url.substr(i)
|
|
536
539
|
try {
|
|
537
540
|
const parsed = new URL(url, 'http://localhost.com/')
|
|
538
|
-
|
|
541
|
+
const parsedPathname = parsed.pathname
|
|
542
|
+
return parsedPathname + (parsedPathname[parsedPathname.length - 1] !== '/' ? '/' : '') + (parsed.search || '')
|
|
539
543
|
} catch (error) {
|
|
540
544
|
// the try-catch here is actually unreachable, but we keep it for safety and prevent DoS attack
|
|
541
545
|
/* istanbul ignore next */
|
|
@@ -548,7 +552,7 @@ function getRedirectUrl (url) {
|
|
|
548
552
|
}
|
|
549
553
|
|
|
550
554
|
module.exports = fp(fastifyStatic, {
|
|
551
|
-
fastify: '4.
|
|
555
|
+
fastify: '^4.23.0',
|
|
552
556
|
name: '@fastify/static'
|
|
553
557
|
})
|
|
554
558
|
module.exports.default = fastifyStatic
|
package/lib/dirList.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const fs = require('fs')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const fs = require('node:fs/promises')
|
|
5
5
|
const pLimit = require('p-limit')
|
|
6
6
|
|
|
7
7
|
const dirList = {
|
|
@@ -16,7 +16,7 @@ const dirList = {
|
|
|
16
16
|
const entries = { dirs: [], files: [] }
|
|
17
17
|
let files = await fs.readdir(dir)
|
|
18
18
|
if (dotfiles === 'deny' || dotfiles === 'ignore') {
|
|
19
|
-
files = files.filter(
|
|
19
|
+
files = files.filter(file => file.charAt(0) !== '.')
|
|
20
20
|
}
|
|
21
21
|
if (files.length < 1) {
|
|
22
22
|
return entries
|
|
@@ -92,6 +92,7 @@ const dirList = {
|
|
|
92
92
|
|
|
93
93
|
entries.dirs.sort((a, b) => a.name.localeCompare(b.name))
|
|
94
94
|
entries.files.sort((a, b) => a.name.localeCompare(b.name))
|
|
95
|
+
|
|
95
96
|
return entries
|
|
96
97
|
},
|
|
97
98
|
|
|
@@ -141,7 +142,7 @@ const dirList = {
|
|
|
141
142
|
* @return {ListFile}
|
|
142
143
|
*/
|
|
143
144
|
htmlInfo: function (entry, route, prefix, options) {
|
|
144
|
-
if (options.names
|
|
145
|
+
if (options.names?.includes(path.basename(route))) {
|
|
145
146
|
route = path.normalize(path.join(route, '..'))
|
|
146
147
|
}
|
|
147
148
|
return {
|
|
@@ -159,12 +160,9 @@ const dirList = {
|
|
|
159
160
|
* @return {boolean}
|
|
160
161
|
*/
|
|
161
162
|
handle: function (route, options) {
|
|
162
|
-
|
|
163
|
-
return false
|
|
164
|
-
}
|
|
165
|
-
return options.names.includes(path.basename(route)) ||
|
|
163
|
+
return options.names?.includes(path.basename(route)) ||
|
|
166
164
|
// match trailing slash
|
|
167
|
-
(options.names
|
|
165
|
+
((options.names?.includes('/') && route[route.length - 1] === '/') ?? false)
|
|
168
166
|
},
|
|
169
167
|
|
|
170
168
|
/**
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/static",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.11.1",
|
|
4
4
|
"description": "Plugin for serving static files as fast as possible.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
9
|
-
"lint
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
8
|
+
"coverage": "npm run test:unit -- --coverage-report=html",
|
|
9
|
+
"lint": "npm run lint:javascript && npm run lint:typescript",
|
|
10
|
+
"lint:javascript": "standard | snazzy",
|
|
11
|
+
"lint:fix": "standard --fix && npm run lint:typescript -- --fix",
|
|
12
|
+
"lint:typescript": "eslint -c .eslintrc.json types/**/*.d.ts types/**/*.test-d.ts",
|
|
13
|
+
"test": "npm run test:unit && npm run test:typescript",
|
|
14
|
+
"test:typescript": "tsd",
|
|
15
|
+
"test:unit": "tap",
|
|
16
|
+
"example": "node example/server.js"
|
|
16
17
|
},
|
|
17
18
|
"repository": {
|
|
18
19
|
"type": "git",
|
|
@@ -29,20 +30,19 @@
|
|
|
29
30
|
},
|
|
30
31
|
"homepage": "https://github.com/fastify/fastify-static",
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"content-disposition": "^0.5.3",
|
|
33
33
|
"@fastify/accept-negotiator": "^1.0.0",
|
|
34
|
+
"@fastify/send": "^2.0.0",
|
|
35
|
+
"content-disposition": "^0.5.3",
|
|
34
36
|
"fastify-plugin": "^4.0.0",
|
|
35
37
|
"glob": "^8.0.1",
|
|
36
|
-
"p-limit": "^3.1.0"
|
|
37
|
-
"readable-stream": "^4.0.0",
|
|
38
|
-
"@fastify/send": "^2.0.0"
|
|
38
|
+
"p-limit": "^3.1.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@fastify/compress": "^6.0.0",
|
|
42
42
|
"@fastify/pre-commit": "^2.0.2",
|
|
43
43
|
"@types/node": "^20.1.0",
|
|
44
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
45
|
-
"@typescript-eslint/parser": "^
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
|
45
|
+
"@typescript-eslint/parser": "^6.3.0",
|
|
46
46
|
"concat-stream": "^2.0.0",
|
|
47
47
|
"coveralls": "^3.0.4",
|
|
48
48
|
"eslint-plugin-typescript": "^0.14.0",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"snazzy": "^9.0.0",
|
|
55
55
|
"standard": "^17.0.0",
|
|
56
56
|
"tap": "^16.0.0",
|
|
57
|
-
"tsd": "^0.
|
|
57
|
+
"tsd": "^0.29.0",
|
|
58
|
+
"typescript": "^5.1.6"
|
|
58
59
|
},
|
|
59
60
|
"tsd": {
|
|
60
61
|
"directory": "test/types"
|
package/test/dir-list.test.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint n/no-deprecated-api: "off" */
|
|
4
4
|
|
|
5
|
-
const fs = require('fs')
|
|
6
|
-
const path = require('path')
|
|
5
|
+
const fs = require('node:fs')
|
|
6
|
+
const path = require('node:path')
|
|
7
7
|
const t = require('tap')
|
|
8
8
|
const simple = require('simple-get')
|
|
9
9
|
const Fastify = require('fastify')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
example
|
package/test/static.test.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint n/no-deprecated-api: "off" */
|
|
4
4
|
|
|
5
|
-
const path = require('path')
|
|
6
|
-
const fs = require('fs')
|
|
7
|
-
const url = require('url')
|
|
8
|
-
const http = require('http')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const url = require('node:url')
|
|
8
|
+
const http = require('node:http')
|
|
9
9
|
const t = require('tap')
|
|
10
10
|
const simple = require('simple-get')
|
|
11
11
|
const Fastify = require('fastify')
|
|
@@ -2823,7 +2823,7 @@ t.test(
|
|
|
2823
2823
|
'register with rootpath that causes statSync to fail with non-ENOENT code',
|
|
2824
2824
|
(t) => {
|
|
2825
2825
|
const fastifyStatic = proxyquire('../', {
|
|
2826
|
-
fs: {
|
|
2826
|
+
'node:fs': {
|
|
2827
2827
|
statSync: function statSyncStub (path) {
|
|
2828
2828
|
throw new Error({ code: 'MOCK' })
|
|
2829
2829
|
}
|
|
@@ -3756,6 +3756,7 @@ t.test(
|
|
|
3756
3756
|
t.end()
|
|
3757
3757
|
}
|
|
3758
3758
|
)
|
|
3759
|
+
|
|
3759
3760
|
t.test(
|
|
3760
3761
|
'converts URL to path',
|
|
3761
3762
|
async (t) => {
|
|
@@ -3803,3 +3804,39 @@ t.test(
|
|
|
3803
3804
|
t.same(response.body, foobarContent)
|
|
3804
3805
|
}
|
|
3805
3806
|
)
|
|
3807
|
+
|
|
3808
|
+
t.test(
|
|
3809
|
+
'serves files with paths that have characters modified by encodeUri when wildcard is false',
|
|
3810
|
+
async (t) => {
|
|
3811
|
+
const aContent = fs.readFileSync(path.join(__dirname, 'static-encode/[...]', 'a .md'), 'utf-8')
|
|
3812
|
+
|
|
3813
|
+
t.plan(4)
|
|
3814
|
+
const pluginOptions = {
|
|
3815
|
+
root: url.pathToFileURL(path.join(__dirname, '/static-encode')),
|
|
3816
|
+
wildcard: false
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
const fastify = Fastify()
|
|
3820
|
+
|
|
3821
|
+
fastify.register(fastifyStatic, pluginOptions)
|
|
3822
|
+
const response = await fastify.inject({
|
|
3823
|
+
method: 'GET',
|
|
3824
|
+
url: '[...]/a .md',
|
|
3825
|
+
headers: {
|
|
3826
|
+
'accept-encoding': '*, *'
|
|
3827
|
+
}
|
|
3828
|
+
})
|
|
3829
|
+
t.equal(response.statusCode, 200)
|
|
3830
|
+
t.same(response.body, aContent)
|
|
3831
|
+
|
|
3832
|
+
const response2 = await fastify.inject({
|
|
3833
|
+
method: 'GET',
|
|
3834
|
+
url: '%5B...%5D/a%20.md',
|
|
3835
|
+
headers: {
|
|
3836
|
+
'accept-encoding': '*, *'
|
|
3837
|
+
}
|
|
3838
|
+
})
|
|
3839
|
+
t.equal(response2.statusCode, 200)
|
|
3840
|
+
t.same(response2.body, aContent)
|
|
3841
|
+
}
|
|
3842
|
+
)
|
package/types/index.d.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Leo <https://github.com/leomelzer>
|
|
3
3
|
/// <reference types="node" />
|
|
4
4
|
|
|
5
|
-
import {FastifyPluginAsync, FastifyRequest, RouteOptions} from 'fastify'
|
|
6
|
-
import { Stats } from 'fs'
|
|
5
|
+
import { FastifyPluginAsync, FastifyRequest, RouteOptions } from 'fastify'
|
|
6
|
+
import { Stats } from 'fs'
|
|
7
7
|
|
|
8
|
-
declare module
|
|
8
|
+
declare module 'fastify' {
|
|
9
9
|
interface FastifyReply {
|
|
10
10
|
sendFile(filename: string, rootPath?: string): FastifyReply;
|
|
11
11
|
sendFile(filename: string, options?: fastifyStatic.SendOptions): FastifyReply;
|
|
@@ -77,7 +77,7 @@ declare namespace fastifyStatic {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export interface FastifyStaticOptions extends SendOptions {
|
|
80
|
-
root: string | string[];
|
|
80
|
+
root: string | string[] | URL | URL[];
|
|
81
81
|
prefix?: string;
|
|
82
82
|
prefixAvoidTrailingSlash?: boolean;
|
|
83
83
|
serve?: boolean;
|
|
@@ -107,9 +107,9 @@ declare namespace fastifyStatic {
|
|
|
107
107
|
constraints?: RouteOptions['constraints'];
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
export const fastifyStatic: FastifyStaticPlugin
|
|
110
|
+
export const fastifyStatic: FastifyStaticPlugin
|
|
111
111
|
|
|
112
|
-
export { fastifyStatic as default }
|
|
112
|
+
export { fastifyStatic as default }
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
declare function fastifyStatic(...params: Parameters<FastifyStaticPlugin>): ReturnType<FastifyStaticPlugin>;
|
package/types/index.test-d.ts
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
import fastify, { FastifyInstance, FastifyPluginAsync, FastifyRequest } from 'fastify'
|
|
2
|
-
import { Server } from 'http'
|
|
2
|
+
import { Server } from 'http'
|
|
3
3
|
import { expectAssignable, expectError, expectType } from 'tsd'
|
|
4
|
-
import * as fastifyStaticStar from '..'
|
|
4
|
+
import * as fastifyStaticStar from '..'
|
|
5
5
|
import fastifyStatic, {
|
|
6
6
|
FastifyStaticOptions,
|
|
7
|
-
fastifyStatic as fastifyStaticNamed
|
|
7
|
+
fastifyStatic as fastifyStaticNamed
|
|
8
8
|
} from '..'
|
|
9
9
|
|
|
10
10
|
import fastifyStaticCjsImport = require('..');
|
|
11
|
-
const fastifyStaticCjs = require('..')
|
|
12
|
-
|
|
13
|
-
const app: FastifyInstance = fastify()
|
|
14
|
-
|
|
15
|
-
app.register(fastifyStatic)
|
|
16
|
-
app.register(fastifyStaticNamed)
|
|
17
|
-
app.register(fastifyStaticCjs)
|
|
18
|
-
app.register(fastifyStaticCjsImport.default)
|
|
19
|
-
app.register(fastifyStaticCjsImport.fastifyStatic)
|
|
20
|
-
app.register(fastifyStaticStar.default)
|
|
21
|
-
app.register(fastifyStaticStar.fastifyStatic)
|
|
22
|
-
|
|
23
|
-
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStatic)
|
|
24
|
-
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticNamed)
|
|
25
|
-
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticCjsImport.default)
|
|
26
|
-
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticCjsImport.fastifyStatic)
|
|
27
|
-
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticStar.default)
|
|
11
|
+
const fastifyStaticCjs = require('..')
|
|
12
|
+
|
|
13
|
+
const app: FastifyInstance = fastify()
|
|
14
|
+
|
|
15
|
+
app.register(fastifyStatic)
|
|
16
|
+
app.register(fastifyStaticNamed)
|
|
17
|
+
app.register(fastifyStaticCjs)
|
|
18
|
+
app.register(fastifyStaticCjsImport.default)
|
|
19
|
+
app.register(fastifyStaticCjsImport.fastifyStatic)
|
|
20
|
+
app.register(fastifyStaticStar.default)
|
|
21
|
+
app.register(fastifyStaticStar.fastifyStatic)
|
|
22
|
+
|
|
23
|
+
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStatic)
|
|
24
|
+
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticNamed)
|
|
25
|
+
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticCjsImport.default)
|
|
26
|
+
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticCjsImport.fastifyStatic)
|
|
27
|
+
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticStar.default)
|
|
28
28
|
expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(
|
|
29
|
-
fastifyStaticStar.fastifyStatic
|
|
30
|
-
)
|
|
31
|
-
expectType<any>(fastifyStaticCjs)
|
|
29
|
+
fastifyStaticStar.fastifyStatic
|
|
30
|
+
)
|
|
31
|
+
expectType<any>(fastifyStaticCjs)
|
|
32
32
|
|
|
33
33
|
const appWithImplicitHttp = fastify()
|
|
34
34
|
const options: FastifyStaticOptions = {
|
|
@@ -54,7 +54,7 @@ const options: FastifyStaticOptions = {
|
|
|
54
54
|
},
|
|
55
55
|
preCompressed: false,
|
|
56
56
|
allowedPath: (pathName: string, root: string, request: FastifyRequest) => {
|
|
57
|
-
return true
|
|
57
|
+
return true
|
|
58
58
|
},
|
|
59
59
|
constraints: {
|
|
60
60
|
host: /.*\.example\.com/,
|
|
@@ -70,7 +70,7 @@ expectError<FastifyStaticOptions>({
|
|
|
70
70
|
expectAssignable<FastifyStaticOptions>({
|
|
71
71
|
root: '',
|
|
72
72
|
list: {
|
|
73
|
-
format: 'json'
|
|
73
|
+
format: 'json'
|
|
74
74
|
}
|
|
75
75
|
})
|
|
76
76
|
|
|
@@ -93,10 +93,22 @@ expectAssignable<FastifyStaticOptions>({
|
|
|
93
93
|
expectError<FastifyStaticOptions>({
|
|
94
94
|
root: '',
|
|
95
95
|
list: {
|
|
96
|
-
format: 'html'
|
|
96
|
+
format: 'html'
|
|
97
97
|
}
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
+
expectAssignable<FastifyStaticOptions>({
|
|
101
|
+
root: ['']
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
expectAssignable<FastifyStaticOptions>({
|
|
105
|
+
root: new URL('')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
expectAssignable<FastifyStaticOptions>({
|
|
109
|
+
root: [new URL('')]
|
|
110
|
+
})
|
|
111
|
+
|
|
100
112
|
appWithImplicitHttp
|
|
101
113
|
.register(fastifyStatic, options)
|
|
102
114
|
.after(() => {
|
|
@@ -123,7 +135,7 @@ appWithHttp2
|
|
|
123
135
|
})
|
|
124
136
|
|
|
125
137
|
appWithHttp2.get('/download/2', (request, reply) => {
|
|
126
|
-
reply.download('some-file-name', 'some-filename'
|
|
138
|
+
reply.download('some-file-name', 'some-filename', { cacheControl: false, acceptRanges: true })
|
|
127
139
|
})
|
|
128
140
|
})
|
|
129
141
|
|
|
@@ -168,7 +180,17 @@ noIndexApp
|
|
|
168
180
|
noIndexApp.get('/', (request, reply) => {
|
|
169
181
|
reply.send('<h1>fastify-static</h1>')
|
|
170
182
|
})
|
|
171
|
-
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
options.root = new URL('')
|
|
186
|
+
|
|
187
|
+
const URLRootApp = fastify()
|
|
188
|
+
URLRootApp.register(fastifyStatic, options)
|
|
189
|
+
.after(() => {
|
|
190
|
+
URLRootApp.get('/', (request, reply) => {
|
|
191
|
+
reply.send('<h1>fastify-static</h1>')
|
|
192
|
+
})
|
|
193
|
+
})
|
|
172
194
|
|
|
173
195
|
const defaultIndexApp = fastify()
|
|
174
196
|
options.index = 'index.html'
|
|
Binary file
|