fix: avoid exception in console for null byte

Massimo Melina committed Jan 19, 2026 at 11:38 UTC e226ebe654b5caed9b126eafb83076f436b8c087
2 files changed +7 -3
src/zip.ts
+5 -2
@@ -20,7 +20,10 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
20 ctx.mime = 'zip'
21 // ctx.query.list is undefined | string | string[]
22 const name = list?.length === 1 ? safeDecodeURIComponent(basename(list[0]!), '') : getNodeName(node)
23 - forceDownload(ctx, (isWindowsDrive(name) ? name[0] : (name || 'archive')) + '.zip')
23 + try {
24 + forceDownload(ctx, (isWindowsDrive(name) ? name[0] : (name || 'archive')) + '.zip')
25 + }
26 + catch { return ctx.status = 400 }
27 const { filterName, filterComment } = paramsToFilter(ctx.query)
28 const walker = !list ? walkNode(node, { ctx, requiredPerm: 'can_archive' })
29 : (async function*(): AsyncIterableIterator<VfsNode> {
@@ -29,7 +32,7 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
32 const subNode = await urlToNode(uri, ctx, node)
33 if (!subNode)
34 continue
32 - if (nodeIsFolder(subNode)) { // a directory needs to walked
35 + if (nodeIsFolder(subNode)) { // a directory needs to be walked
36 if (hasPermission(subNode, 'can_list', ctx) && hasPermission(subNode, 'can_archive', ctx)) {
37 yield subNode // it could be empty
38 yield* walkNode(subNode, { ctx, prefixPath: decodeURI(uri) + '/', requiredPerm: 'can_archive' })
tests/test.ts
+2 -1
@@ -147,7 +147,8 @@ describe('basics', () => {
147 test('zip.partial', req('/f1/?get=zip', { re:/^page$/, length: zipLength }, { headers: { Range: `bytes=${zipOfs}-${zipOfs+zipLength-1}` } }) )
148 test('zip.partial.resume', req('/f1/?get=zip', { re:/^page/, length:zipSize-zipOfs }, { headers: { Range: `bytes=${zipOfs}-` } }) )
149 test('zip.partial.end', req('/f1/f2/?get=zip', { re:/^6/, length:10 }, { headers: { Range: 'bytes=-10' } }) )
150 - test('zip.list.bad encoding', req('/f1/?get=zip&list=%E0%A4%A', { status: 200, length: 22 })) // basically empty
150 + test('zip.list.bad encoding', req('/f1/?get=zip&list=%E0%A4%A//%00', { status: 200, length: 22 })) // basically empty
151 + test('zip.list.null filename', req('/f1/?get=zip&list=%00', 400)) // tries to name the output with null-byte
152 test('zip.alfa is forbidden', req('/protectFromAbove/child/?get=zip&list=alfa.txt//renamed', { empty: true, length:134 }, { method:'HEAD' }))
153 test('zip.cantReadPage', req('/cantReadPage/?get=zip', { length: 4832 }, { method:'HEAD' }))
154