fix: admin/logs: crash in case of illegal url parameters #615
Massimo Melina committed
Jun 9, 2024 at 23:36 UTC
414ab63f47b56464486a03bbdbb7e661fd00e7e4
3 files changed
+10
-5
admin/src/LogsPage.ts
+2
-2
@@ -5,7 +5,7 @@ import { Box, Tab, Tabs } from '@mui/material'
5
import { API_URL, useApi, useApiList } from './api'
6
import { DataTable } from './DataTable'
7
import { CFG, Dict, formatBytes, HTTP_UNAUTHORIZED, newDialog, prefix, shortenAgent, splitAt, tryJson, md,
8
- typedKeys, NBSP, _dbg, mapFilter } from '@hfs/shared'
8
+ typedKeys, NBSP, _dbg, mapFilter, safeDecodeURIComponent } from '@hfs/shared'
9
import { logLabels } from './OptionsPage'
10
import {
11
NetmaskField, Flex, IconBtn, useBreakpoint, usePauseButton, useToggleButton, WildcardsSupported, Country,
@@ -237,7 +237,7 @@ function LogFile({ file, addToFooter, hidden }: { hidden?: boolean, file: string
237
minWidth: 100,
238
mergeRender: { other: 'method', fontSize: 'small' },
239
renderCell: ({ value, row }) => {
240
- const [path, query] = splitAt('?', value).map(decodeURIComponent)
240
+ const [path, query] = splitAt('?', value).map(safeDecodeURIComponent)
241
const ul = row.extra?.ul
242
if (ul)
243
return typeof ul === 'string' ? ul // legacy pre-0.51
src/cross.ts
+5
@@ -468,6 +468,11 @@ export function mapFilter<T=unknown, R=T>(arr: T[], map: (x:T, idx: number) => R
468
}, [] as R[])
469
}
470
471
+export function safeDecodeURIComponent(s: string) {
472
+ try { return decodeURIComponent(s) }
473
+ catch { return s }
474
+}
475
+
476
export function shortenAgent(agent: string) {
477
return _.findKey(BROWSERS, re => re.test(agent))
478
|| /^[^/(]+ ?/.exec(agent)?.[0]
src/zip.ts
+3
-3
@@ -2,7 +2,7 @@
2
3
import { getNodeName, hasPermission, nodeIsDirectory, nodeIsLink, urlToNode, VfsNode, walkNode, statusCodeForMissingPerm } from './vfs'
4
import Koa from 'koa'
5
-import { filterMapGenerator, isWindowsDrive, pattern2filter, wantArray } from './misc'
5
+import { filterMapGenerator, isWindowsDrive, pattern2filter, safeDecodeURIComponent, wantArray } from './misc'
6
import { QuickZipStream } from './QuickZipStream'
7
import { createReadStream } from 'fs'
8
import fs from 'fs/promises'
@@ -18,7 +18,7 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
18
ctx.mime = 'zip'
19
// ctx.query.list is undefined | string | string[]
20
const list = wantArray(ctx.query.list)[0]?.split('*') // we are using * as separator because it cannot be used in a file name and doesn't need url encoding
21
- const name = list?.length === 1 ? decodeURIComponent(basename(list[0]!)) : getNodeName(node)
21
+ const name = list?.length === 1 ? safeDecodeURIComponent(basename(list[0]!)) : getNodeName(node)
22
ctx.attachment((isWindowsDrive(name) ? name[0] : (name || 'archive')) + '.zip')
23
const filter = pattern2filter(String(ctx.query.search||''))
24
const walker = !list ? walkNode(node, { ctx, requiredPerm: 'can_archive' })
@@ -34,7 +34,7 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
34
}
35
continue
36
}
37
- let folder = dirname(decodeURIComponent(uri)) // decodeURI() won't account for %23=#
37
+ let folder = dirname(safeDecodeURIComponent(uri)) // decodeURI() won't account for %23=#
38
folder = folder === '.' ? '' : folder + '/'
39
yield { ...subNode, name: folder + getNodeName(subNode) } // reflect relative path in archive, otherwise way may have name-clashes
40
}