@samitouri / QOSami-HFS / commits / 8321d321

fix: admin/logs: crash in case of illegal url parameters #615

Massimo Melina committed Jun 9, 2024 at 23:36 UTC 8321d321425e6d53f0d8c1d7c481df784bebf48d
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, apiCall, 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 {
10 NetmaskField, Flex, IconBtn, useBreakpoint, usePauseButton, useToggleButton, WildcardsSupported, Country,
11 hTooltip, Btn, wikiLink
@@ -269,7 +269,7 @@ function LogFile({ file, addToFooter, hidden }: { hidden?: boolean, file: string
269 sx: { wordBreak: 'break-all' }, // be flexible, uri can be a mess
270 mergeRender: { method: {}, status: {} },
271 renderCell: ({ value, row }) => {
272 - const [path, query] = splitAt('?', value).map(decodeURIComponent)
272 + const [path, query] = splitAt('?', value).map(safeDecodeURIComponent)
273 const ul = row.extra?.ul
274 if (ul)
275 return typeof ul === 'string' ? ul // legacy pre-0.51
src/cross.ts
+5
@@ -461,6 +461,11 @@ export function callable<T>(x: T | ((...args: unknown[]) => T), ...args: unknown
461 return _.isFunction(x) ? x(...args) : x
462 }
463
464 +export function safeDecodeURIComponent(s: string) {
465 + try { return decodeURIComponent(s) }
466 + catch { return s }
467 +}
468 +
469 export function shortenAgent(agent: string) {
470 return _.findKey(BROWSERS, re => re.test(agent))
471 || /^[^/(]+ ?/.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 }