fix: admin/internet: crash if an IPv6 (no square brackets) is entered as main address

Massimo Melina committed Jun 8, 2026 at 15:27 UTC fad79616a1cb93653108a817622e18dc2423939a
2 files changed +9 -7
admin/src/FileForm.ts
+5 -4
@@ -10,7 +10,8 @@ import { apiCall, UseApi, useApiEx } from './api'
10 import {
11 basename, defaultPerms, formatBytes, formatTimestamp, isWhoObject, newDialog, useRequestRender, try_, pathEncode,
12 onlyTruthy, prefix, VfsPerms, wantArray, WhoVfs, WhoObject, matches, xlate, md, Callback, copyTextToClipboard,
13 - splitAt, IMAGE_FILEMASK, CFG, MASK_IN_TESTS, WHO_ANY_ACCOUNT, WHO_ADMIN, WHO_NO_ONE, WHO_ANYONE,
13 + splitAt, IMAGE_FILEMASK, CFG, MASK_IN_TESTS, WHO_ANY_ACCOUNT, WHO_ADMIN, WHO_NO_ONE, WHO_ANYONE, stringBefore,
14 + ipForUrl
15 } from './misc'
16 import { isModifiedConfig } from './AccountForm'
17 import { Btn, Flex, IconBtn, LinkBtn, propsForModifiedValues, useBreakpoint, wikiLink } from './mui'
@@ -294,7 +295,7 @@ export function WhoField({ value, onChange, parent, inherit, accountsApi, helper
295
296 const timeout = 500
297 const arrayMode = Array.isArray(thisValue)
297 - // a large side band will convey union across the fields
298 + // a large sideband will convey union across the fields
299 return h(Box, { sx: { borderRight: objectMode ? '8px solid #8884' : undefined, transition: `all ${timeout}ms` } },
300 h(SelectField as typeof SelectField<typeof thisValue | null>, {
301 ...rest,
@@ -466,7 +467,7 @@ export async function changeBaseUrl() {
467 title: "Main address",
468 Content() {
469 const [v, setV] = useState(base_url || '')
469 - const proto = new URL(v || urls[0]).protocol + '//'
470 + const proto = stringBefore('//', v || urls[0]) + '//'
471 const host = urls.includes(v) ? '' : v.slice(proto.length)
472 const check = h(Check, { sx: { ml: 2 } })
473 return h(Box, { sx: { display: 'flex', flexDirection: 'column' } },
@@ -486,7 +487,7 @@ export async function changeBaseUrl() {
487 label: "Custom IP or domain",
488 helperText: md("You can type any address but *you* are responsible to make the address work.\nThis functionality is just to help you copy the link in case you have a domain or a complex network configuration."),
489 value: host,
489 - onChange: v => set(prefix(proto, v)),
490 + onChange: v => set(prefix(proto, ipForUrl(v))),
491 start: h(SelectField as Field<string>, {
492 value: proto,
493 onChange: v => host ? set(v + host) : toast("Enter domain first"),
src/cross.ts
+4 -3
@@ -179,9 +179,9 @@ export function stringAfter(sub: string, all: string) {
179 return i < 0 ? '' : all.slice(i + sub.length)
180 }
181
182 -export function stringBefore(sub: string, all: string, returnEmptyWhenSubMissing=true) {
182 +export function stringBefore(sub: string, all: string, returnEmptyWhenSubIsMissing=true) {
183 const i = all.indexOf(sub)
184 - return i >= 0 ? all.slice(0, i + sub.length - 1) : returnEmptyWhenSubMissing ? '' : all
184 + return i >= 0 ? all.slice(0, i) : returnEmptyWhenSubIsMissing ? '' : all
185 }
186
187 export function truthy<T>(value: T): value is Truthy<T> {
@@ -461,7 +461,8 @@ export function isIpLan(ip: string) {
461 }
462
463 export function ipForUrl(ip: string) {
464 - return ip.includes(':') ? '[' + ip + ']' : ip
464 + const i = ip.indexOf(':')
465 + return i >= 0 && ip.indexOf(':', i + 1) >= 0 ? `[${ip}]` : ip
466 }
467
468 export function escapeHTML(text: string) {