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) {