@fastify/static 7.0.3 → 8.0.0-pre.fv5.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.
Files changed (70) hide show
  1. package/.eslintrc.json +0 -0
  2. package/.gitattributes +0 -0
  3. package/.github/dependabot.yml +0 -0
  4. package/.github/stale.yml +0 -0
  5. package/.github/workflows/ci.yml +1 -1
  6. package/.taprc +0 -0
  7. package/LICENSE +0 -0
  8. package/README.md +3 -3
  9. package/example/public/.hidden/sample.json +0 -0
  10. package/example/public/images/sample.jpg +0 -0
  11. package/example/public/index.css +0 -0
  12. package/example/public/index.html +0 -0
  13. package/example/public/index.js +0 -0
  14. package/example/public2/test.css +0 -0
  15. package/example/public2/test.html +0 -0
  16. package/example/server-compress.js +0 -0
  17. package/example/server-dir-list.js +0 -0
  18. package/example/server-hidden-file.js +0 -0
  19. package/example/server.js +0 -0
  20. package/index.js +128 -149
  21. package/lib/dirList.js +4 -4
  22. package/package.json +21 -22
  23. package/test/content-type/binary +0 -0
  24. package/test/content-type/binary.br +0 -0
  25. package/test/content-type/index.css +0 -0
  26. package/test/content-type/index.css.br +0 -0
  27. package/test/content-type/index.html +0 -0
  28. package/test/content-type/index.html.br +0 -0
  29. package/test/content-type/sample.jpg +0 -0
  30. package/test/content-type/test.txt +0 -0
  31. package/test/content-type/test.txt.br +0 -0
  32. package/test/content-type.test.js +0 -0
  33. package/test/dir-list.test.js +38 -8
  34. package/test/static/.example +0 -0
  35. package/test/static/100%.txt +1 -0
  36. package/test/static/a .md +0 -0
  37. package/test/static/deep/path/for/test/index.html +0 -0
  38. package/test/static/deep/path/for/test/purpose/foo.html +0 -0
  39. package/test/static/foo.html +0 -0
  40. package/test/static/foobar.html +0 -0
  41. package/test/static/index.css +0 -0
  42. package/test/static/index.html +0 -0
  43. package/test/static/shallow/sample.jpg +0 -0
  44. package/test/static-dotfiles/.aaa +0 -0
  45. package/test/static-dotfiles/dir/index.html +0 -0
  46. package/test/static-dotfiles/test.txt +0 -0
  47. package/test/static-encode/[...]/a .md +0 -0
  48. package/test/static-hidden/.hidden/sample.json +0 -0
  49. package/test/static-pre-compressed/all-three.html +0 -0
  50. package/test/static-pre-compressed/all-three.html.br +0 -0
  51. package/test/static-pre-compressed/all-three.html.gz +0 -0
  52. package/test/static-pre-compressed/dir/index.html +0 -0
  53. package/test/static-pre-compressed/dir/index.html.br +0 -0
  54. package/test/static-pre-compressed/dir-gz/index.html +0 -0
  55. package/test/static-pre-compressed/dir-gz/index.html.gz +0 -0
  56. package/test/static-pre-compressed/empty/.gitkeep +0 -0
  57. package/test/static-pre-compressed/gzip-only.html +0 -0
  58. package/test/static-pre-compressed/gzip-only.html.gz +0 -0
  59. package/test/static-pre-compressed/index.html +0 -0
  60. package/test/static-pre-compressed/index.html.br +0 -0
  61. package/test/static-pre-compressed/sample.jpg +0 -0
  62. package/test/static-pre-compressed/sample.jpg.br +0 -0
  63. package/test/static-pre-compressed/uncompressed.html +0 -0
  64. package/test/static-symbolic-link/origin/subdir/subdir/index.html +0 -0
  65. package/test/static.test.js +31 -4
  66. package/test/static2/bar.html +0 -0
  67. package/test/static2/index.html +0 -0
  68. package/tsconfig.eslint.json +0 -0
  69. package/types/index.d.ts +0 -0
  70. package/types/index.test-d.ts +0 -0
package/.eslintrc.json CHANGED
File without changes
package/.gitattributes CHANGED
File without changes
File without changes
package/.github/stale.yml CHANGED
File without changes
@@ -17,7 +17,7 @@ on:
17
17
 
18
18
  jobs:
19
19
  test:
20
- uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3
20
+ uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5.0.0
21
21
  with:
22
22
  license-check: true
23
23
  lint: true
package/.taprc CHANGED
File without changes
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -477,13 +477,13 @@ If an error occurs while trying to send a file, the error will be passed
477
477
  to Fastify's error handler. You can set a custom error handler with
478
478
  [`fastify.setErrorHandler()`](https://fastify.dev/docs/latest/Reference/Server/#seterrorhandler).
479
479
 
480
- ### Payload `stream.filename`
480
+ ### Payload `stream.path`
481
481
 
482
- If you need to access the filename inside the `onSend` hook, you can use `payload.filename`.
482
+ If you need to access the file path inside the `onSend` hook, you can use `payload.path`.
483
483
 
484
484
  ```js
485
485
  fastify.addHook('onSend', function (req, reply, payload, next) {
486
- console.log(payload.filename)
486
+ console.log(payload.path)
487
487
  next()
488
488
  })
489
489
  ```
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/example/server.js CHANGED
File without changes
package/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { PassThrough } = require('node:stream')
4
3
  const path = require('node:path')
5
4
  const { fileURLToPath } = require('node:url')
6
5
  const { statSync } = require('node:fs')
@@ -121,7 +120,7 @@ async function fastifyStatic (fastify, opts) {
121
120
  })
122
121
  if (opts.redirect === true && prefix !== opts.prefix) {
123
122
  fastify.get(opts.prefix, routeOpts, (req, reply) => {
124
- reply.redirect(301, getRedirectUrl(req.raw.url))
123
+ reply.redirect(getRedirectUrl(req.raw.url), 301)
125
124
  })
126
125
  }
127
126
  } else {
@@ -170,7 +169,7 @@ async function fastifyStatic (fastify, opts) {
170
169
 
171
170
  const allowedPath = opts.allowedPath
172
171
 
173
- function pumpSendToReply (
172
+ async function pumpSendToReply (
174
173
  request,
175
174
  reply,
176
175
  pathname,
@@ -221,164 +220,149 @@ async function fastifyStatic (fastify, opts) {
221
220
  }
222
221
  }
223
222
 
224
- const stream = send(request.raw, pathnameForSend, options)
225
- let resolvedFilename
226
- stream.on('file', function (file) {
227
- resolvedFilename = file
228
- })
229
-
230
- const wrap = new PassThrough({
231
- flush (cb) {
232
- this.finished = true
233
- if (reply.raw.statusCode === 304) {
234
- reply.send('')
223
+ // `send(..., path, ...)` will URI-decode path so we pass an encoded path here
224
+ const {
225
+ statusCode,
226
+ headers,
227
+ stream,
228
+ type,
229
+ metadata
230
+ } = await send(request.raw, encodeURI(pathnameForSend), options)
231
+ switch (type) {
232
+ case 'directory': {
233
+ const path = metadata.path
234
+ if (opts.list) {
235
+ await dirList.send({
236
+ reply,
237
+ dir: path,
238
+ options: opts.list,
239
+ route: pathname,
240
+ prefix,
241
+ dotfiles: opts.dotfiles
242
+ }).catch((err) => reply.send(err))
235
243
  }
236
- cb()
237
- }
238
- })
239
244
 
240
- wrap.getHeader = reply.getHeader.bind(reply)
241
- wrap.setHeader = reply.header.bind(reply)
242
- wrap.removeHeader = () => {}
243
- wrap.finished = false
245
+ if (opts.redirect === true) {
246
+ try {
247
+ reply.redirect(getRedirectUrl(request.raw.url), 301)
248
+ } /* c8 ignore start */ catch (error) {
249
+ // the try-catch here is actually unreachable, but we keep it for safety and prevent DoS attack
250
+ await reply.send(error)
251
+ } /* c8 ignore stop */
252
+ } else {
253
+ // if is a directory path without a trailing slash, and has an index file, reply as if it has a trailing slash
254
+ if (!pathname.endsWith('/') && findIndexFile(pathname, options.root, options.index)) {
255
+ return pumpSendToReply(
256
+ request,
257
+ reply,
258
+ pathname + '/',
259
+ rootPath,
260
+ undefined,
261
+ undefined,
262
+ checkedEncodings
263
+ )
264
+ }
244
265
 
245
- Object.defineProperty(wrap, 'filename', {
246
- get () {
247
- return resolvedFilename
248
- }
249
- })
250
- Object.defineProperty(wrap, 'statusCode', {
251
- get () {
252
- return reply.raw.statusCode
253
- },
254
- set (code) {
255
- reply.code(code)
266
+ reply.callNotFound()
267
+ }
268
+ break
256
269
  }
257
- })
258
-
259
- if (request.method === 'HEAD') {
260
- wrap.on('finish', reply.send.bind(reply))
261
- } else {
262
- wrap.on('pipe', function () {
263
- if (encoding) {
264
- reply.header('content-type', getContentType(pathname))
265
- reply.header('content-encoding', encoding)
270
+ case 'error': {
271
+ if (
272
+ statusCode === 403 &&
273
+ (!options.index || !options.index.length) &&
274
+ pathnameForSend[pathnameForSend.length - 1] === '/'
275
+ ) {
276
+ if (opts.list) {
277
+ await dirList.send({
278
+ reply,
279
+ dir: dirList.path(opts.root, pathname),
280
+ options: opts.list,
281
+ route: pathname,
282
+ prefix,
283
+ dotfiles: opts.dotfiles
284
+ }).catch((err) => reply.send(err))
285
+ }
266
286
  }
267
- reply.send(wrap)
268
- })
269
- }
270
-
271
- if (setHeaders !== undefined) {
272
- stream.on('headers', setHeaders)
273
- }
274
287
 
275
- stream.on('directory', function (_, path) {
276
- if (opts.list) {
277
- dirList.send({
278
- reply,
279
- dir: path,
280
- options: opts.list,
281
- route: pathname,
282
- prefix,
283
- dotfiles: opts.dotfiles
284
- }).catch((err) => reply.send(err))
285
- return
286
- }
288
+ if (metadata.error.code === 'ENOENT') {
289
+ // when preCompress is enabled and the path is a directory without a trailing slash
290
+ if (opts.preCompressed && encoding) {
291
+ const indexPathname = findIndexFile(pathname, options.root, options.index)
292
+ if (indexPathname) {
293
+ return pumpSendToReply(
294
+ request,
295
+ reply,
296
+ pathname + '/',
297
+ rootPath,
298
+ undefined,
299
+ undefined,
300
+ checkedEncodings
301
+ )
302
+ }
303
+ }
287
304
 
288
- if (opts.redirect === true) {
289
- try {
290
- reply.redirect(301, getRedirectUrl(request.raw.url))
291
- } catch (error) {
292
- // the try-catch here is actually unreachable, but we keep it for safety and prevent DoS attack
293
- /* istanbul ignore next */
294
- reply.send(error)
295
- }
296
- } else {
297
- // if is a directory path without a trailing slash, and has an index file, reply as if it has a trailing slash
298
- if (!pathname.endsWith('/') && findIndexFile(pathname, options.root, options.index)) {
299
- return pumpSendToReply(
300
- request,
301
- reply,
302
- pathname + '/',
303
- rootPath,
304
- undefined,
305
- undefined,
306
- checkedEncodings
307
- )
308
- }
305
+ // if file exists, send real file, otherwise send dir list if name match
306
+ if (opts.list && dirList.handle(pathname, opts.list)) {
307
+ await dirList.send({
308
+ reply,
309
+ dir: dirList.path(opts.root, pathname),
310
+ options: opts.list,
311
+ route: pathname,
312
+ prefix,
313
+ dotfiles: opts.dotfiles
314
+ }).catch((err) => reply.send(err))
315
+ return
316
+ }
309
317
 
310
- reply.callNotFound()
311
- }
312
- })
318
+ // root paths left to try?
319
+ if (Array.isArray(rootPath) && rootPathOffset < (rootPath.length - 1)) {
320
+ return pumpSendToReply(request, reply, pathname, rootPath, rootPathOffset + 1)
321
+ }
313
322
 
314
- stream.on('error', function (err) {
315
- if (err.code === 'ENOENT') {
316
- // when preCompress is enabled and the path is a directory without a trailing slash
317
- if (opts.preCompressed && encoding) {
318
- const indexPathname = findIndexFile(pathname, options.root, options.index)
319
- if (indexPathname) {
323
+ if (opts.preCompressed && !checkedEncodings.has(encoding)) {
324
+ checkedEncodings.add(encoding)
320
325
  return pumpSendToReply(
321
326
  request,
322
327
  reply,
323
- pathname + '/',
328
+ pathnameOrig,
324
329
  rootPath,
325
- undefined,
330
+ rootPathOffset,
326
331
  undefined,
327
332
  checkedEncodings
328
333
  )
329
334
  }
330
- }
331
335
 
332
- // if file exists, send real file, otherwise send dir list if name match
333
- if (opts.list && dirList.handle(pathname, opts.list)) {
334
- dirList.send({
335
- reply,
336
- dir: dirList.path(opts.root, pathname),
337
- options: opts.list,
338
- route: pathname,
339
- prefix,
340
- dotfiles: opts.dotfiles
341
- }).catch((err) => reply.send(err))
342
- return
336
+ return reply.callNotFound()
343
337
  }
344
338
 
345
- // root paths left to try?
346
- if (Array.isArray(rootPath) && rootPathOffset < (rootPath.length - 1)) {
347
- return pumpSendToReply(request, reply, pathname, rootPath, rootPathOffset + 1)
339
+ // The `send` library terminates the request with a 404 if the requested
340
+ // path contains a dotfile and `send` is initialized with `{dotfiles:
341
+ // 'ignore'}`. `send` aborts the request before getting far enough to
342
+ // check if the file exists (hence, a 404 `NotFoundError` instead of
343
+ // `ENOENT`).
344
+ // https://github.com/pillarjs/send/blob/de073ed3237ade9ff71c61673a34474b30e5d45b/index.js#L582
345
+ if (metadata.error.status === 404) {
346
+ return reply.callNotFound()
348
347
  }
349
348
 
350
- if (opts.preCompressed && !checkedEncodings.has(encoding)) {
351
- checkedEncodings.add(encoding)
352
- return pumpSendToReply(
353
- request,
354
- reply,
355
- pathnameOrig,
356
- rootPath,
357
- rootPathOffset,
358
- undefined,
359
- checkedEncodings
360
- )
361
- }
362
-
363
- return reply.callNotFound()
349
+ await reply.send(metadata.error)
350
+ break
364
351
  }
365
-
366
- // The `send` library terminates the request with a 404 if the requested
367
- // path contains a dotfile and `send` is initialized with `{dotfiles:
368
- // 'ignore'}`. `send` aborts the request before getting far enough to
369
- // check if the file exists (hence, a 404 `NotFoundError` instead of
370
- // `ENOENT`).
371
- // https://github.com/pillarjs/send/blob/de073ed3237ade9ff71c61673a34474b30e5d45b/index.js#L582
372
- if (err.status === 404) {
373
- return reply.callNotFound()
352
+ case 'file': {
353
+ reply.code(statusCode)
354
+ if (setHeaders !== undefined) {
355
+ setHeaders(reply.raw, metadata.path, metadata.stat)
356
+ }
357
+ reply.headers(headers)
358
+ if (encoding) {
359
+ reply.header('content-type', getContentType(pathname))
360
+ reply.header('content-encoding', encoding)
361
+ }
362
+ await reply.send(stream)
363
+ break
374
364
  }
375
-
376
- reply.send(err)
377
- })
378
-
379
- // we cannot use pump, because send error
380
- // handling is not compatible
381
- stream.pipe(wrap)
365
+ }
382
366
  }
383
367
 
384
368
  function setUpHeadAndGet (routeOpts, route, file, rootPath) {
@@ -393,11 +377,11 @@ async function fastifyStatic (fastify, opts) {
393
377
  fastify.route(toSetUp)
394
378
  }
395
379
 
396
- function serveFileHandler (req, reply) {
380
+ async function serveFileHandler (req, reply) {
397
381
  // TODO: remove the fallback branch when bump major
398
- /* istanbul ignore next */
382
+ /* c8 ignore next */
399
383
  const routeConfig = req.routeOptions?.config || req.routeConfig
400
- pumpSendToReply(req, reply, routeConfig.file, routeConfig.rootPath)
384
+ return pumpSendToReply(req, reply, routeConfig.file, routeConfig.rootPath)
401
385
  }
402
386
  }
403
387
 
@@ -488,8 +472,6 @@ function getContentType (path) {
488
472
  }
489
473
 
490
474
  function findIndexFile (pathname, root, indexFiles = ['index.html']) {
491
- // TODO remove istanbul ignore
492
- /* istanbul ignore else */
493
475
  if (Array.isArray(indexFiles)) {
494
476
  return indexFiles.find(filename => {
495
477
  const p = path.join(root, pathname, filename)
@@ -501,7 +483,7 @@ function findIndexFile (pathname, root, indexFiles = ['index.html']) {
501
483
  }
502
484
  })
503
485
  }
504
- /* istanbul ignore next */
486
+ /* c8 ignore next */
505
487
  return false
506
488
  }
507
489
 
@@ -540,19 +522,16 @@ function getRedirectUrl (url) {
540
522
  const parsed = new URL(url, 'http://localhost.com/')
541
523
  const parsedPathname = parsed.pathname
542
524
  return parsedPathname + (parsedPathname[parsedPathname.length - 1] !== '/' ? '/' : '') + (parsed.search || '')
543
- } catch {
525
+ } /* c8 ignore start */ catch {
544
526
  // the try-catch here is actually unreachable, but we keep it for safety and prevent DoS attack
545
- /* istanbul ignore next */
546
527
  const err = new Error(`Invalid redirect URL: ${url}`)
547
- /* istanbul ignore next */
548
528
  err.statusCode = 400
549
- /* istanbul ignore next */
550
529
  throw err
551
- }
530
+ } /* c8 ignore stop */
552
531
  }
553
532
 
554
533
  module.exports = fp(fastifyStatic, {
555
- fastify: '4.x',
534
+ fastify: '5.x',
556
535
  name: '@fastify/static'
557
536
  })
558
537
  module.exports.default = fastifyStatic
package/lib/dirList.js CHANGED
@@ -125,9 +125,9 @@ const dirList = {
125
125
  entries.dirs.forEach(entry => nameEntries.dirs.push(entry.name))
126
126
  entries.files.forEach(entry => nameEntries.files.push(entry.name))
127
127
 
128
- reply.send(nameEntries)
128
+ await reply.send(nameEntries)
129
129
  } else {
130
- reply.send(entries)
130
+ await reply.send(entries)
131
131
  }
132
132
  return
133
133
  }
@@ -135,7 +135,7 @@ const dirList = {
135
135
  const html = options.render(
136
136
  entries.dirs.map(entry => dirList.htmlInfo(entry, route, prefix, options)),
137
137
  entries.files.map(entry => dirList.htmlInfo(entry, route, prefix, options)))
138
- reply.type('text/html').send(html)
138
+ await reply.type('text/html').send(html)
139
139
  },
140
140
 
141
141
  /**
@@ -149,7 +149,7 @@ const dirList = {
149
149
  route = path.normalize(path.join(route, '..'))
150
150
  }
151
151
  return {
152
- href: path.join(prefix, route, entry.name).replace(/\\/gu, '/'),
152
+ href: encodeURI(path.join(prefix, route, entry.name).replace(/\\/gu, '/')),
153
153
  name: entry.name,
154
154
  stats: entry.stats,
155
155
  extendedInfo: entry.extendedInfo
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/static",
3
- "version": "7.0.3",
3
+ "version": "8.0.0-pre.fv5.1",
4
4
  "description": "Plugin for serving static files as fast as possible.",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -31,32 +31,31 @@
31
31
  },
32
32
  "homepage": "https://github.com/fastify/fastify-static",
33
33
  "dependencies": {
34
- "@fastify/accept-negotiator": "^1.0.0",
35
- "@fastify/send": "^2.0.0",
36
- "content-disposition": "^0.5.3",
37
- "fastify-plugin": "^4.0.0",
38
- "fastq": "^1.17.0",
39
- "glob": "^10.3.4"
34
+ "@fastify/accept-negotiator": "^2.0.0-pre.fv5.1",
35
+ "@fastify/send": "^3.1.0",
36
+ "content-disposition": "^0.5.4",
37
+ "fastify-plugin": "^5.0.0-pre.fv5.1",
38
+ "fastq": "^1.17.1",
39
+ "glob": "^10.3.10"
40
40
  },
41
41
  "devDependencies": {
42
- "@fastify/compress": "^7.0.0",
43
- "@fastify/pre-commit": "^2.0.2",
44
- "@types/node": "^20.1.0",
45
- "@typescript-eslint/eslint-plugin": "^7.1.0",
46
- "@typescript-eslint/parser": "^7.1.0",
42
+ "@fastify/compress": "^8.0.0-pre.fv5.1",
43
+ "@fastify/pre-commit": "^2.1.0",
44
+ "@types/node": "^20.11.30",
45
+ "@typescript-eslint/eslint-plugin": "^7.3.1",
46
+ "@typescript-eslint/parser": "^7.3.1",
47
47
  "concat-stream": "^2.0.0",
48
- "coveralls": "^3.0.4",
49
- "eslint-plugin-typescript": "^0.14.0",
50
- "fastify": "^4.0.0-rc.2",
51
- "handlebars": "^4.7.6",
52
- "pino": "^8.4.2",
53
- "proxyquire": "^2.1.0",
54
- "simple-get": "^4.0.0",
48
+ "coveralls": "^3.1.1",
49
+ "fastify": "^5.0.0-alpha.3",
50
+ "handlebars": "^4.7.8",
51
+ "pino": "^9.1.0",
52
+ "proxyquire": "^2.1.3",
53
+ "simple-get": "^4.0.1",
55
54
  "snazzy": "^9.0.0",
56
- "standard": "^17.0.0",
57
- "tap": "^16.0.0",
55
+ "standard": "^17.1.0",
56
+ "tap": "^18.7.1",
58
57
  "tsd": "^0.31.0",
59
- "typescript": "^5.1.6"
58
+ "typescript": "^5.4.3"
60
59
  },
61
60
  "tsd": {
62
61
  "directory": "test/types"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -154,7 +154,35 @@ t.test('dir list, custom options', t => {
154
154
  }
155
155
 
156
156
  const route = '/public/'
157
- const content = { dirs: ['deep', 'shallow'], files: ['.example', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
157
+ const content = { dirs: ['deep', 'shallow'], files: ['.example', '100%.txt', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
158
+
159
+ helper.arrange(t, options, (url) => {
160
+ t.test(route, t => {
161
+ t.plan(3)
162
+ simple.concat({
163
+ method: 'GET',
164
+ url: url + route
165
+ }, (err, response, body) => {
166
+ t.error(err)
167
+ t.equal(response.statusCode, 200)
168
+ t.equal(body.toString(), JSON.stringify(content))
169
+ })
170
+ })
171
+ })
172
+ })
173
+
174
+ t.test('dir list, custom options with empty array index', t => {
175
+ t.plan(2)
176
+
177
+ const options = {
178
+ root: path.join(__dirname, '/static'),
179
+ prefix: '/public',
180
+ index: [],
181
+ list: true
182
+ }
183
+
184
+ const route = '/public/'
185
+ const content = { dirs: ['deep', 'shallow'], files: ['.example', '100%.txt', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
158
186
 
159
187
  helper.arrange(t, options, (url) => {
160
188
  t.test(route, t => {
@@ -205,7 +233,8 @@ t.test('dir list html format', t => {
205
233
  </ul>
206
234
  <ul>
207
235
  <li><a href="/public/.example" target="_blank">.example</a></li>
208
- <li><a href="/public/a .md" target="_blank">a .md</a></li>
236
+ <li><a href="/public/100%25.txt" target="_blank">100%.txt</a></li>
237
+ <li><a href="/public/a%20.md" target="_blank">a .md</a></li>
209
238
  <li><a href="/public/foo.html" target="_blank">foo.html</a></li>
210
239
  <li><a href="/public/foobar.html" target="_blank">foobar.html</a></li>
211
240
  <li><a href="/public/index.css" target="_blank">index.css</a></li>
@@ -236,7 +265,8 @@ t.test('dir list html format', t => {
236
265
  </ul>
237
266
  <ul>
238
267
  <li><a href="/public/.example" target="_blank">.example</a></li>
239
- <li><a href="/public/a .md" target="_blank">a .md</a></li>
268
+ <li><a href="/public/100%25.txt" target="_blank">100%.txt</a></li>
269
+ <li><a href="/public/a%20.md" target="_blank">a .md</a></li>
240
270
  <li><a href="/public/foo.html" target="_blank">foo.html</a></li>
241
271
  <li><a href="/public/foobar.html" target="_blank">foobar.html</a></li>
242
272
  <li><a href="/public/index.css" target="_blank">index.css</a></li>
@@ -469,7 +499,7 @@ t.test('dir list json format - extended info', t => {
469
499
  t.equal(response.statusCode, 200)
470
500
  const bodyObject = JSON.parse(body.toString())
471
501
  t.equal(bodyObject.dirs[0].name, 'empty')
472
- t.equal(typeof bodyObject.dirs[0].stats.atime, 'string')
502
+ t.equal(typeof bodyObject.dirs[0].stats.atimeMs, 'number')
473
503
  t.equal(typeof bodyObject.dirs[0].extendedInfo.totalSize, 'number')
474
504
  })
475
505
  })
@@ -492,7 +522,7 @@ t.test('json format with url parameter format', t => {
492
522
  }
493
523
  }
494
524
  const route = '/public/'
495
- const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
525
+ const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', '100%.txt', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
496
526
 
497
527
  helper.arrange(t, options, (url) => {
498
528
  simple.concat({
@@ -539,7 +569,7 @@ t.test('json format with url parameter format and without render option', t => {
539
569
  }
540
570
  }
541
571
  const route = '/public/'
542
- const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
572
+ const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', '100%.txt', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
543
573
 
544
574
  helper.arrange(t, options, (url) => {
545
575
  simple.concat({
@@ -588,7 +618,7 @@ t.test('html format with url parameter format', t => {
588
618
  }
589
619
  }
590
620
  const route = '/public/'
591
- const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
621
+ const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', '100%.txt', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
592
622
 
593
623
  helper.arrange(t, options, (url) => {
594
624
  simple.concat({
@@ -843,7 +873,7 @@ t.test('dir list error', t => {
843
873
  const errorMessage = 'mocking send'
844
874
  dirList.send = async () => { throw new Error(errorMessage) }
845
875
 
846
- const mock = t.mock('..', {
876
+ const mock = t.mockRequire('..', {
847
877
  '../lib/dirList.js': dirList
848
878
  })
849
879
 
File without changes
@@ -0,0 +1 @@
1
+ 100%
package/test/static/a .md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -681,7 +681,7 @@ t.test('register /static with constraints', (t) => {
681
681
  })
682
682
  })
683
683
 
684
- t.test('payload.filename is set', (t) => {
684
+ t.test('payload.path is set', (t) => {
685
685
  t.plan(3)
686
686
 
687
687
  const pluginOptions = {
@@ -692,7 +692,7 @@ t.test('payload.filename is set', (t) => {
692
692
  let gotFilename
693
693
  fastify.register(fastifyStatic, pluginOptions)
694
694
  fastify.addHook('onSend', function (req, reply, payload, next) {
695
- gotFilename = payload.filename
695
+ gotFilename = payload.path
696
696
  next()
697
697
  })
698
698
 
@@ -1361,13 +1361,13 @@ t.test('root not found warning', (t) => {
1361
1361
  const destination = concat((data) => {
1362
1362
  t.equal(JSON.parse(data).msg, `"root" path "${rootPath}" must exist`)
1363
1363
  })
1364
- const logger = pino(
1364
+ const loggerInstance = pino(
1365
1365
  {
1366
1366
  level: 'warn'
1367
1367
  },
1368
1368
  destination
1369
1369
  )
1370
- const fastify = Fastify({ logger })
1370
+ const fastify = Fastify({ loggerInstance })
1371
1371
  fastify.register(fastifyStatic, pluginOptions)
1372
1372
  fastify.listen({ port: 0 }, (err) => {
1373
1373
  t.error(err)
@@ -3963,6 +3963,33 @@ t.test(
3963
3963
  }
3964
3964
  )
3965
3965
 
3966
+ t.test(
3967
+ 'serves files with % in the filename',
3968
+ async (t) => {
3969
+ t.plan(2)
3970
+
3971
+ const txtContent = fs.readFileSync(path.join(__dirname, 'static', '100%.txt'), 'utf-8')
3972
+
3973
+ const pluginOptions = {
3974
+ root: url.pathToFileURL(path.join(__dirname, '/static')),
3975
+ wildcard: false
3976
+ }
3977
+
3978
+ const fastify = Fastify()
3979
+
3980
+ fastify.register(fastifyStatic, pluginOptions)
3981
+ const response = await fastify.inject({
3982
+ method: 'GET',
3983
+ url: '100%25.txt',
3984
+ headers: {
3985
+ 'accept-encoding': '*, *'
3986
+ }
3987
+ })
3988
+ t.equal(response.statusCode, 200)
3989
+ t.same(response.body, txtContent)
3990
+ }
3991
+ )
3992
+
3966
3993
  t.test('content-length in head route should not return zero when using wildcard', t => {
3967
3994
  t.plan(6)
3968
3995
 
File without changes
File without changes
File without changes
package/types/index.d.ts CHANGED
File without changes
File without changes