@samitouri / QOSami-HFS / commits / 0f89620c

fix: admin/shared: link field not working for roots with special chars; also fall back to other roots that are not baseUrl

Massimo Melina committed May 28, 2026 at 10:17 UTC 0f89620c3e997f3cbb15bedb20acfe0fa8363dd0
2 files changed +29 -7
admin/src/FileForm.ts
+28 -6
@@ -8,7 +8,7 @@ import {
8 } from '@hfs/mui-grid-form'
9 import { apiCall, UseApi, useApiEx } from './api'
10 import {
11 - basename, defaultPerms, formatBytes, formatTimestamp, isWhoObject, newDialog, useRequestRender, try_,
11 + basename, defaultPerms, formatBytes, formatTimestamp, isWhoObject, newDialog, useRequestRender, try_, pathEncode,
12 onlyTruthy, prefix, VfsPerms, wantArray, WhoVfs, WhoObject, matches, xlate, md, Callback, copyTextToClipboard,
13 normalizeHost, splitAt, IMAGE_FILEMASK, CFG, MASK_IN_TESTS, WHO_ANY_ACCOUNT, WHO_ADMIN, WHO_NO_ONE, WHO_ANYONE,
14 } from './misc'
@@ -365,12 +365,28 @@ function LinkField({ value, statusApi }: LinkFieldProps) {
365 const data = statusApi.getData()
366
367 const urls: string[] = data && (data.urls.https || data.urls.http || [data.base_url])
368 - const baseHost = try_(() => normalizeHost(new URL(data?.baseUrl).host)) // URL can throw on malformed data
369 - const root = useMemo(() => baseHost && _.find(data.roots, (_root, host) => matches(baseHost, host)),
370 - [data])
368 + const baseHost = try_(() => new URL(data?.baseUrl).host) // URL can throw on malformed data
369 + const roots = data?.roots || {}
370 + const root = baseHost && _.find(roots, (_root, host) => matches(baseHost, host))
371 + const originalValue = value
372 if (root)
372 - value &&= value.indexOf(root) === 1 ? value.slice(root.length) : undefined
373 - const link = prefix(data?.baseUrl || '', value)
373 + value = pathInRoot(value, root)
374 + let linkBase = data?.baseUrl || ''
375 + if (value === undefined) { // baseUrl didn't match, but other hosts in roots may
376 + const base = try_(() => new URL(linkBase))
377 + if (base) {
378 + const sorted = _.sortBy(Object.entries(roots), ([, root]) => -String(root).length) // prioritize longer roots because are more specific
379 + for (const [hostMask, root] of sorted) {
380 + if (typeof root !== 'string') continue
381 + value = pathInRoot(originalValue, root)
382 + const host = value && hostMask.split('|').find(x => x && !/[*?]/.test(x) && x !== baseHost)
383 + if (!host) continue
384 + linkBase = base.protocol + '//' + host
385 + break
386 + }
387 + }
388 + }
389 + const link = prefix(linkBase, value)
390 const RenderLink = useMemo(() => forwardRef((props: any, ref) =>
391 h(Link, {
392 ref,
@@ -430,6 +446,12 @@ function LinkField({ value, statusApi }: LinkFieldProps) {
446 console.error('Error generating QR code:', error)
447 }
448 }
449 +
450 + function pathInRoot(uri: string | undefined, root: string | undefined) {
451 + if (!root || root === '/') return uri
452 + root = pathEncode(root)
453 + return uri?.startsWith(root, 1) ? uri.slice(root.length) : undefined
454 + }
455 }
456
457 export async function changeBaseUrl() {
shared/index.ts
+1 -1
@@ -136,7 +136,7 @@ export function getHFS() {
136 }
137
138 export function getPrefixUrl() {
139 - return getHFS().prefixUrl
139 + return getHFS().prefixUrl || ''
140 }
141
142 export function makeSessionRefresher(state: any) {