@fastify/static 9.0.0 → 9.1.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/.github/workflows/ci.yml +1 -1
- package/.github/workflows/lock-threads.yml +19 -0
- package/LICENSE +1 -3
- package/index.js +77 -23
- package/lib/dirList.js +20 -6
- package/package.json +5 -5
- package/test/dir-list.test.js +82 -0
- package/test/static/shallow/no-link +1 -0
- package/test/static-symbolic-link/dir/symlink +1 -0
- package/test/static.test.js +114 -4
- package/types/index.d.ts +0 -4
- package/types/index.test-d.ts +1 -1
- package/.github/stale.yml +0 -21
package/.github/workflows/ci.yml
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Lock Threads
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '0 0 1 * *'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: lock
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lock-threads:
|
|
16
|
+
permissions:
|
|
17
|
+
issues: write
|
|
18
|
+
pull-requests: write
|
|
19
|
+
uses: fastify/workflows/.github/workflows/lock-threads.yml@v6
|
package/LICENSE
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2017-present The Fastify team
|
|
4
|
-
|
|
5
|
-
The Fastify team members are listed at https://github.com/fastify/fastify#team.
|
|
3
|
+
Copyright (c) 2017-present The Fastify team <https://github.com/fastify/fastify#team>
|
|
6
4
|
|
|
7
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/index.js
CHANGED
|
@@ -122,7 +122,12 @@ async function fastifyStatic (fastify, opts) {
|
|
|
122
122
|
method: ['HEAD', 'GET'],
|
|
123
123
|
path: prefix + '*',
|
|
124
124
|
handler (req, reply) {
|
|
125
|
-
|
|
125
|
+
const pathname = getPathnameForSend(req.raw.url, prefix)
|
|
126
|
+
if (!pathname) {
|
|
127
|
+
return reply.callNotFound()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
pumpSendToReply(req, reply, pathname, sendOptions.root)
|
|
126
131
|
}
|
|
127
132
|
})
|
|
128
133
|
if (opts.redirect === true && prefix !== opts.prefix) {
|
|
@@ -176,6 +181,29 @@ async function fastifyStatic (fastify, opts) {
|
|
|
176
181
|
|
|
177
182
|
const allowedPath = opts.allowedPath
|
|
178
183
|
|
|
184
|
+
/**
|
|
185
|
+
* @param {import("fastify").FastifyReply} reply
|
|
186
|
+
* @param {string} pathname
|
|
187
|
+
* @return {Promise<boolean>}
|
|
188
|
+
*/
|
|
189
|
+
async function sendDirectoryList (reply, pathname) {
|
|
190
|
+
const dir = dirList.path(opts.root, pathname)
|
|
191
|
+
if (!dir) {
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await dirList.send({
|
|
196
|
+
reply,
|
|
197
|
+
dir,
|
|
198
|
+
options: opts.list,
|
|
199
|
+
route: pathname,
|
|
200
|
+
prefix,
|
|
201
|
+
dotfiles: opts.dotfiles
|
|
202
|
+
}).catch((err) => reply.send(err))
|
|
203
|
+
|
|
204
|
+
return true
|
|
205
|
+
}
|
|
206
|
+
|
|
179
207
|
/**
|
|
180
208
|
* @param {import("fastify").FastifyRequest} request
|
|
181
209
|
* @param {import("fastify").FastifyReply} reply
|
|
@@ -274,7 +302,7 @@ async function fastifyStatic (fastify, opts) {
|
|
|
274
302
|
pathname + '/',
|
|
275
303
|
rootPath,
|
|
276
304
|
undefined,
|
|
277
|
-
|
|
305
|
+
pumpOptions,
|
|
278
306
|
checkedEncodings
|
|
279
307
|
)
|
|
280
308
|
}
|
|
@@ -289,15 +317,7 @@ async function fastifyStatic (fastify, opts) {
|
|
|
289
317
|
(!options.index || !options.index.length) &&
|
|
290
318
|
pathnameForSend[pathnameForSend.length - 1] === '/'
|
|
291
319
|
) {
|
|
292
|
-
if (opts.list) {
|
|
293
|
-
await dirList.send({
|
|
294
|
-
reply,
|
|
295
|
-
dir: dirList.path(opts.root, pathname),
|
|
296
|
-
options: opts.list,
|
|
297
|
-
route: pathname,
|
|
298
|
-
prefix,
|
|
299
|
-
dotfiles: opts.dotfiles
|
|
300
|
-
}).catch((err) => reply.send(err))
|
|
320
|
+
if (opts.list && await sendDirectoryList(reply, pathname)) {
|
|
301
321
|
return
|
|
302
322
|
}
|
|
303
323
|
}
|
|
@@ -314,7 +334,7 @@ async function fastifyStatic (fastify, opts) {
|
|
|
314
334
|
pathname + '/',
|
|
315
335
|
rootPath,
|
|
316
336
|
undefined,
|
|
317
|
-
|
|
337
|
+
pumpOptions,
|
|
318
338
|
checkedEncodings
|
|
319
339
|
)
|
|
320
340
|
}
|
|
@@ -322,21 +342,21 @@ async function fastifyStatic (fastify, opts) {
|
|
|
322
342
|
}
|
|
323
343
|
|
|
324
344
|
// if file exists, send real file, otherwise send dir list if name match
|
|
325
|
-
if (opts.list && dirList.handle(pathname, opts.list)) {
|
|
326
|
-
await dirList.send({
|
|
327
|
-
reply,
|
|
328
|
-
dir: dirList.path(opts.root, pathname),
|
|
329
|
-
options: opts.list,
|
|
330
|
-
route: pathname,
|
|
331
|
-
prefix,
|
|
332
|
-
dotfiles: opts.dotfiles
|
|
333
|
-
}).catch((err) => reply.send(err))
|
|
345
|
+
if (opts.list && dirList.handle(pathname, opts.list) && await sendDirectoryList(reply, pathname)) {
|
|
334
346
|
return
|
|
335
347
|
}
|
|
336
348
|
|
|
337
349
|
// root paths left to try?
|
|
338
350
|
if (Array.isArray(rootPath) && rootPathOffset < (rootPath.length - 1)) {
|
|
339
|
-
return pumpSendToReply(
|
|
351
|
+
return pumpSendToReply(
|
|
352
|
+
request,
|
|
353
|
+
reply,
|
|
354
|
+
pathname,
|
|
355
|
+
rootPath,
|
|
356
|
+
rootPathOffset + 1,
|
|
357
|
+
pumpOptions,
|
|
358
|
+
undefined
|
|
359
|
+
)
|
|
340
360
|
}
|
|
341
361
|
|
|
342
362
|
if (opts.preCompressed && !checkedEncodings.has(encoding)) {
|
|
@@ -347,7 +367,7 @@ async function fastifyStatic (fastify, opts) {
|
|
|
347
367
|
pathnameOrig,
|
|
348
368
|
rootPath,
|
|
349
369
|
rootPathOffset,
|
|
350
|
-
|
|
370
|
+
pumpOptions,
|
|
351
371
|
checkedEncodings
|
|
352
372
|
)
|
|
353
373
|
}
|
|
@@ -548,6 +568,40 @@ function getEncodingHeader (headers, checked) {
|
|
|
548
568
|
)
|
|
549
569
|
}
|
|
550
570
|
|
|
571
|
+
/**
|
|
572
|
+
* @param {string} url
|
|
573
|
+
* @param {string} prefix
|
|
574
|
+
* @returns {string|undefined}
|
|
575
|
+
*/
|
|
576
|
+
function getPathnameForSend (url, prefix) {
|
|
577
|
+
const questionMark = url.indexOf('?')
|
|
578
|
+
let pathname = questionMark === -1 ? url : url.slice(0, questionMark)
|
|
579
|
+
|
|
580
|
+
if (prefix !== '/') {
|
|
581
|
+
const routePrefix = prefix.endsWith('/')
|
|
582
|
+
? prefix.slice(0, -1)
|
|
583
|
+
: prefix
|
|
584
|
+
|
|
585
|
+
if (!pathname.startsWith(routePrefix)) {
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
pathname = pathname.slice(routePrefix.length)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (pathname === '') {
|
|
593
|
+
pathname = '/'
|
|
594
|
+
} else if (!pathname.startsWith('/')) {
|
|
595
|
+
pathname = '/' + pathname
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
return decodeURI(pathname)
|
|
600
|
+
} catch {
|
|
601
|
+
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
551
605
|
/**
|
|
552
606
|
* @param {string} url
|
|
553
607
|
* @return {string}
|
package/lib/dirList.js
CHANGED
|
@@ -6,6 +6,16 @@ const fs = require('node:fs/promises')
|
|
|
6
6
|
const fastq = require('fastq')
|
|
7
7
|
const fastqConcurrency = Math.max(1, os.cpus().length - 1)
|
|
8
8
|
|
|
9
|
+
function isPathInsideRoot (root, target) {
|
|
10
|
+
const relativePath = path.relative(root, target)
|
|
11
|
+
|
|
12
|
+
return relativePath === '' || (
|
|
13
|
+
relativePath !== '..' &&
|
|
14
|
+
!relativePath.startsWith(`..${path.sep}`) &&
|
|
15
|
+
!path.isAbsolute(relativePath)
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
const dirList = {
|
|
10
20
|
_getExtendedInfo: async function (dir, info) {
|
|
11
21
|
const depth = dir.split(path.sep).length
|
|
@@ -146,10 +156,10 @@ const dirList = {
|
|
|
146
156
|
*/
|
|
147
157
|
htmlInfo: function (entry, route, prefix, options) {
|
|
148
158
|
if (options.names?.includes(path.basename(route))) {
|
|
149
|
-
route = path.normalize(path.join(route, '..'))
|
|
159
|
+
route = path.posix.normalize(path.posix.join(route, '..'))
|
|
150
160
|
}
|
|
151
161
|
return {
|
|
152
|
-
href: encodeURI(path.join(prefix, route, entry.name)
|
|
162
|
+
href: encodeURI(path.posix.join(prefix, route, entry.name)),
|
|
153
163
|
name: entry.name,
|
|
154
164
|
stats: entry.stats,
|
|
155
165
|
extendedInfo: entry.extendedInfo
|
|
@@ -172,12 +182,16 @@ const dirList = {
|
|
|
172
182
|
* get path from route and fs root paths, considering trailing slash
|
|
173
183
|
* @param {string} root fs root path
|
|
174
184
|
* @param {string} route request route
|
|
185
|
+
* @return {string|null}
|
|
175
186
|
*/
|
|
176
187
|
path: function (root, route) {
|
|
177
|
-
const
|
|
178
|
-
? route + 'none'
|
|
179
|
-
: route
|
|
180
|
-
|
|
188
|
+
const relativeRoute = route[route.length - 1] === '/'
|
|
189
|
+
? '.' + route + 'none'
|
|
190
|
+
: '.' + route
|
|
191
|
+
const resolvedRoot = path.resolve(root)
|
|
192
|
+
const dir = path.dirname(path.resolve(resolvedRoot, relativeRoute))
|
|
193
|
+
|
|
194
|
+
return isPathInsideRoot(resolvedRoot, dir) ? dir : null
|
|
181
195
|
},
|
|
182
196
|
|
|
183
197
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/static",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.1",
|
|
4
4
|
"description": "Plugin for serving static files as fast as possible.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@fastify/compress": "^8.0.0",
|
|
70
|
-
"@types/node": "^
|
|
71
|
-
"borp": "^0.
|
|
72
|
-
"c8": "^
|
|
70
|
+
"@types/node": "^25.0.3",
|
|
71
|
+
"borp": "^1.0.0",
|
|
72
|
+
"c8": "^11.0.0",
|
|
73
73
|
"concat-stream": "^2.0.0",
|
|
74
74
|
"eslint": "^9.17.0",
|
|
75
75
|
"fastify": "^5.1.0",
|
|
76
76
|
"neostandard": "^0.12.0",
|
|
77
|
-
"pino": "^
|
|
77
|
+
"pino": "^10.0.0",
|
|
78
78
|
"proxyquire": "^2.1.3",
|
|
79
79
|
"tsd": "^0.33.0"
|
|
80
80
|
},
|
package/test/dir-list.test.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/* eslint n/no-deprecated-api: "off" */
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs')
|
|
6
|
+
const http = require('node:http')
|
|
6
7
|
const path = require('node:path')
|
|
7
8
|
const { test } = require('node:test')
|
|
8
9
|
const Fastify = require('fastify')
|
|
@@ -18,6 +19,31 @@ const helper = {
|
|
|
18
19
|
await fastify.listen({ port: 0 })
|
|
19
20
|
fastify.server.unref()
|
|
20
21
|
await f('http://localhost:' + fastify.server.address().port)
|
|
22
|
+
},
|
|
23
|
+
rawRequest: async function (baseUrl, route) {
|
|
24
|
+
const url = new URL(baseUrl)
|
|
25
|
+
|
|
26
|
+
return await new Promise((resolve, reject) => {
|
|
27
|
+
const request = http.request({
|
|
28
|
+
hostname: url.hostname,
|
|
29
|
+
port: url.port,
|
|
30
|
+
path: route,
|
|
31
|
+
method: 'GET'
|
|
32
|
+
}, (response) => {
|
|
33
|
+
let body = ''
|
|
34
|
+
response.setEncoding('utf8')
|
|
35
|
+
response.on('data', chunk => { body += chunk })
|
|
36
|
+
response.on('end', () => {
|
|
37
|
+
resolve({
|
|
38
|
+
statusCode: response.statusCode,
|
|
39
|
+
body
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
request.on('error', reject)
|
|
45
|
+
request.end()
|
|
46
|
+
})
|
|
21
47
|
}
|
|
22
48
|
}
|
|
23
49
|
|
|
@@ -65,6 +91,16 @@ test('throws when `list.format` is html and `list render` is not a function', t
|
|
|
65
91
|
t.assert.deepStrictEqual(err.message, 'The `list.render` option must be a function and is required with html format')
|
|
66
92
|
})
|
|
67
93
|
|
|
94
|
+
test('dir list path stays within the configured root', t => {
|
|
95
|
+
t.plan(3)
|
|
96
|
+
|
|
97
|
+
const root = path.join(__dirname, '/static')
|
|
98
|
+
|
|
99
|
+
t.assert.deepStrictEqual(dirList.path(root, '/'), root)
|
|
100
|
+
t.assert.deepStrictEqual(dirList.path(root, '/deep/path/'), path.join(root, 'deep/path'))
|
|
101
|
+
t.assert.deepStrictEqual(dirList.path(root, '/../'), null)
|
|
102
|
+
})
|
|
103
|
+
|
|
68
104
|
test('dir list wrong options', async t => {
|
|
69
105
|
t.plan(3)
|
|
70
106
|
|
|
@@ -181,6 +217,52 @@ test('dir list, custom options with empty array index', async t => {
|
|
|
181
217
|
})
|
|
182
218
|
})
|
|
183
219
|
|
|
220
|
+
test('dir list blocks path traversal when index is false', async t => {
|
|
221
|
+
t.plan(2)
|
|
222
|
+
|
|
223
|
+
const options = {
|
|
224
|
+
root: path.join(__dirname, '/static'),
|
|
225
|
+
prefix: '/public',
|
|
226
|
+
index: false,
|
|
227
|
+
list: true
|
|
228
|
+
}
|
|
229
|
+
const routes = ['/public/../', '/public/../static2/']
|
|
230
|
+
|
|
231
|
+
await helper.arrange(t, options, async (url) => {
|
|
232
|
+
for (const route of routes) {
|
|
233
|
+
await t.test(route, async t => {
|
|
234
|
+
t.plan(2)
|
|
235
|
+
|
|
236
|
+
const response = await helper.rawRequest(url, route)
|
|
237
|
+
t.assert.deepStrictEqual(response.statusCode, 403)
|
|
238
|
+
t.assert.match(response.body, /Forbidden/u)
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('dir list blocks path traversal when index is an empty array', async t => {
|
|
245
|
+
t.plan(1)
|
|
246
|
+
|
|
247
|
+
const options = {
|
|
248
|
+
root: path.join(__dirname, '/static'),
|
|
249
|
+
prefix: '/public',
|
|
250
|
+
index: [],
|
|
251
|
+
list: true
|
|
252
|
+
}
|
|
253
|
+
const route = '/public/../'
|
|
254
|
+
|
|
255
|
+
await helper.arrange(t, options, async (url) => {
|
|
256
|
+
await t.test(route, async t => {
|
|
257
|
+
t.plan(2)
|
|
258
|
+
|
|
259
|
+
const response = await helper.rawRequest(url, route)
|
|
260
|
+
t.assert.deepStrictEqual(response.statusCode, 403)
|
|
261
|
+
t.assert.match(response.body, /Forbidden/u)
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
184
266
|
test('dir list html format', async t => {
|
|
185
267
|
t.plan(2)
|
|
186
268
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/none
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../origin
|
package/test/static.test.js
CHANGED
|
@@ -870,6 +870,83 @@ test('sendFile', async (t) => {
|
|
|
870
870
|
})
|
|
871
871
|
})
|
|
872
872
|
|
|
873
|
+
test('sendFile with multiple roots', async (t) => {
|
|
874
|
+
t.plan(4)
|
|
875
|
+
|
|
876
|
+
const pluginOptions = {
|
|
877
|
+
root: [path.join(__dirname, '/static'), path.join(__dirname, '/static2')],
|
|
878
|
+
prefix: '/static'
|
|
879
|
+
}
|
|
880
|
+
const fastify = Fastify()
|
|
881
|
+
const maxAge = Math.round(Math.random() * 10) * 10000
|
|
882
|
+
fastify.register(fastifyStatic, pluginOptions)
|
|
883
|
+
|
|
884
|
+
t.after(() => fastify.close())
|
|
885
|
+
|
|
886
|
+
fastify.get('/foo', function (_req, rep) {
|
|
887
|
+
rep.sendFile('foo.html')
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
fastify.get('/foo-with-options', function (_req, rep) {
|
|
891
|
+
rep.sendFile('foo.html', { maxAge })
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
fastify.get('/bar', function (_req, rep) {
|
|
895
|
+
rep.sendFile('bar.html')
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
fastify.get('/bar-with-options', function (_req, rep) {
|
|
899
|
+
rep.sendFile('bar.html', { maxAge })
|
|
900
|
+
})
|
|
901
|
+
|
|
902
|
+
await fastify.listen({ port: 0 })
|
|
903
|
+
fastify.server.unref()
|
|
904
|
+
|
|
905
|
+
await t.test('reply.sendFile() first root', async (t) => {
|
|
906
|
+
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
|
|
907
|
+
|
|
908
|
+
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo')
|
|
909
|
+
t.assert.ok(response.ok)
|
|
910
|
+
t.assert.deepStrictEqual(response.status, 200)
|
|
911
|
+
t.assert.deepStrictEqual(await response.text(), fooContent)
|
|
912
|
+
t.assert.deepStrictEqual(response.headers.get('cache-control'), 'public, max-age=0')
|
|
913
|
+
genericResponseChecks(t, response)
|
|
914
|
+
})
|
|
915
|
+
|
|
916
|
+
await t.test('reply.sendFile() first root with options', async (t) => {
|
|
917
|
+
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
|
|
918
|
+
|
|
919
|
+
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo-with-options')
|
|
920
|
+
t.assert.ok(response.ok)
|
|
921
|
+
t.assert.deepStrictEqual(response.status, 200)
|
|
922
|
+
t.assert.deepStrictEqual(await response.text(), fooContent)
|
|
923
|
+
t.assert.deepStrictEqual(response.headers.get('cache-control'), `public, max-age=${maxAge / 1000}`)
|
|
924
|
+
genericResponseChecks(t, response)
|
|
925
|
+
})
|
|
926
|
+
|
|
927
|
+
await t.test('reply.sendFile() second root', async (t) => {
|
|
928
|
+
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
|
|
929
|
+
|
|
930
|
+
const response = await fetch('http://localhost:' + fastify.server.address().port + '/bar')
|
|
931
|
+
t.assert.ok(response.ok)
|
|
932
|
+
t.assert.deepStrictEqual(response.status, 200)
|
|
933
|
+
t.assert.deepStrictEqual(await response.text(), barContent)
|
|
934
|
+
t.assert.deepStrictEqual(response.headers.get('cache-control'), 'public, max-age=0')
|
|
935
|
+
genericResponseChecks(t, response)
|
|
936
|
+
})
|
|
937
|
+
|
|
938
|
+
await t.test('reply.sendFile() second root with options', async (t) => {
|
|
939
|
+
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
|
|
940
|
+
|
|
941
|
+
const response = await fetch('http://localhost:' + fastify.server.address().port + '/bar-with-options')
|
|
942
|
+
t.assert.ok(response.ok)
|
|
943
|
+
t.assert.deepStrictEqual(response.status, 200)
|
|
944
|
+
t.assert.deepStrictEqual(await response.text(), barContent)
|
|
945
|
+
t.assert.deepStrictEqual(response.headers.get('cache-control'), `public, max-age=${maxAge / 1000}`)
|
|
946
|
+
genericResponseChecks(t, response)
|
|
947
|
+
})
|
|
948
|
+
})
|
|
949
|
+
|
|
873
950
|
test('sendFile disabled', async (t) => {
|
|
874
951
|
t.plan(1)
|
|
875
952
|
|
|
@@ -1438,7 +1515,7 @@ test('register no prefix', async (t) => {
|
|
|
1438
1515
|
})
|
|
1439
1516
|
})
|
|
1440
1517
|
|
|
1441
|
-
test('with fastify-compress', async t => {
|
|
1518
|
+
test('with fastify-compress', { timeout: 60000 }, async t => {
|
|
1442
1519
|
t.plan(2)
|
|
1443
1520
|
|
|
1444
1521
|
const pluginOptions = {
|
|
@@ -1476,6 +1553,7 @@ test('with fastify-compress', async t => {
|
|
|
1476
1553
|
genericResponseChecks(t, response)
|
|
1477
1554
|
})
|
|
1478
1555
|
})
|
|
1556
|
+
|
|
1479
1557
|
test('register /static/ with schemaHide true', async t => {
|
|
1480
1558
|
t.plan(2)
|
|
1481
1559
|
|
|
@@ -1712,7 +1790,9 @@ test('register with wildcard false (trailing slash in the root)', async t => {
|
|
|
1712
1790
|
wildcard: false
|
|
1713
1791
|
}
|
|
1714
1792
|
const fastify = Fastify({
|
|
1715
|
-
|
|
1793
|
+
routerOptions: {
|
|
1794
|
+
ignoreTrailingSlash: true
|
|
1795
|
+
}
|
|
1716
1796
|
})
|
|
1717
1797
|
fastify.register(fastifyStatic, pluginOptions)
|
|
1718
1798
|
|
|
@@ -3174,8 +3254,8 @@ test('should not redirect to protocol-relative locations', async (t) => {
|
|
|
3174
3254
|
['//^/..', '/', 301],
|
|
3175
3255
|
['//^/.', null, 404], // it is NOT recognized as a directory by pillarjs/send
|
|
3176
3256
|
['//:/..', '/', 301],
|
|
3177
|
-
['/\\\\a//google.com/%2e%2e%2f%2e%2e',
|
|
3178
|
-
['//a//youtube.com/%2e%2e%2f%2e%2e',
|
|
3257
|
+
['/\\\\a//google.com/%2e%2e%2f%2e%2e', null, 404],
|
|
3258
|
+
['//a//youtube.com/%2e%2e%2f%2e%2e', null, 404],
|
|
3179
3259
|
['/^', null, 404], // it is NOT recognized as a directory by pillarjs/send
|
|
3180
3260
|
['//google.com/%2e%2e', '/', 301],
|
|
3181
3261
|
['//users/%2e%2e', '/', 301],
|
|
@@ -3510,6 +3590,36 @@ test(
|
|
|
3510
3590
|
}
|
|
3511
3591
|
)
|
|
3512
3592
|
|
|
3593
|
+
test('does not serve static files with encoded path separators', async (t) => {
|
|
3594
|
+
t.plan(4)
|
|
3595
|
+
|
|
3596
|
+
const fastify = Fastify()
|
|
3597
|
+
|
|
3598
|
+
t.after(() => fastify.close())
|
|
3599
|
+
|
|
3600
|
+
fastify.all('/deep/*', async (_request, reply) => {
|
|
3601
|
+
reply.code(401).send('Unauthorized')
|
|
3602
|
+
})
|
|
3603
|
+
|
|
3604
|
+
fastify.register(fastifyStatic, {
|
|
3605
|
+
root: path.join(__dirname, '/static')
|
|
3606
|
+
})
|
|
3607
|
+
|
|
3608
|
+
const response = await fastify.inject({
|
|
3609
|
+
method: 'GET',
|
|
3610
|
+
url: '/deep/path/for/test/purpose/foo.html'
|
|
3611
|
+
})
|
|
3612
|
+
t.assert.deepStrictEqual(response.statusCode, 401)
|
|
3613
|
+
t.assert.deepStrictEqual(response.body, 'Unauthorized')
|
|
3614
|
+
|
|
3615
|
+
const encodedPathResponse = await fastify.inject({
|
|
3616
|
+
method: 'GET',
|
|
3617
|
+
url: '/deep%2Fpath/for/test/purpose/foo.html'
|
|
3618
|
+
})
|
|
3619
|
+
t.assert.deepStrictEqual(encodedPathResponse.statusCode, 404)
|
|
3620
|
+
t.assert.deepStrictEqual(encodedPathResponse.json().message, 'Route GET:/deep%2Fpath/for/test/purpose/foo.html not found')
|
|
3621
|
+
})
|
|
3622
|
+
|
|
3513
3623
|
test('content-length in head route should not return zero when using wildcard', async t => {
|
|
3514
3624
|
t.plan(5)
|
|
3515
3625
|
|
package/types/index.d.ts
CHANGED
package/types/index.test-d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import fastifyStatic, {
|
|
|
8
8
|
fastifyStatic as fastifyStaticNamed
|
|
9
9
|
} from '..'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const fastifyStaticCjsImport = fastifyStaticStar
|
|
12
12
|
const fastifyStaticCjs = require('..')
|
|
13
13
|
|
|
14
14
|
const app: FastifyInstance = fastify()
|
package/.github/stale.yml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Number of days of inactivity before an issue becomes stale
|
|
2
|
-
daysUntilStale: 15
|
|
3
|
-
# Number of days of inactivity before a stale issue is closed
|
|
4
|
-
daysUntilClose: 7
|
|
5
|
-
# Issues with these labels will never be considered stale
|
|
6
|
-
exemptLabels:
|
|
7
|
-
- "discussion"
|
|
8
|
-
- "feature request"
|
|
9
|
-
- "bug"
|
|
10
|
-
- "help wanted"
|
|
11
|
-
- "plugin suggestion"
|
|
12
|
-
- "good first issue"
|
|
13
|
-
# Label to use when marking an issue as stale
|
|
14
|
-
staleLabel: stale
|
|
15
|
-
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
16
|
-
markComment: >
|
|
17
|
-
This issue has been automatically marked as stale because it has not had
|
|
18
|
-
recent activity. It will be closed if no further activity occurs. Thank you
|
|
19
|
-
for your contributions.
|
|
20
|
-
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
21
|
-
closeComment: false
|