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 }