From 7dabc40c85c91bfcce4938e5d3bffc17393bd112 Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Tue, 22 Sep 2026 04:11:33 +0800 Subject: [PATCH] fix: bound custom URL length before persistence --- admin/app/validators/system.ts | 7 +++++-- admin/inertia/components/AppUrlModal.tsx | 1 + admin/inertia/lib/navigation.ts | 5 +++-- admin/tests/unit/custom_url.spec.ts | 15 +++++++++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 admin/tests/unit/custom_url.spec.ts diff --git a/admin/app/validators/system.ts b/admin/app/validators/system.ts index 1153931d..c4e557b9 100644 --- a/admin/app/validators/system.ts +++ b/admin/app/validators/system.ts @@ -101,7 +101,7 @@ export const customAppValidator = vine.compile( export const setServiceCustomUrlValidator = vine.compile( vine.object({ service_name: vine.string().trim(), - custom_url: vine.string().trim().nullable(), + custom_url: vine.string().trim().maxLength(255).nullable(), }) ) @@ -118,7 +118,10 @@ export function normalizeCustomUrl(input: string | null | undefined): string | n try { const url = new URL(withScheme) if (url.protocol !== 'http:' && url.protocol !== 'https:') return null - return url.href + // services.custom_url uses the database driver's default VARCHAR(255) size. + // Check the normalized value because prepending a scheme or URL canonicalization + // can make a valid-looking input longer than the stored column. + return url.href.length <= 255 ? url.href : null } catch { return null } diff --git a/admin/inertia/components/AppUrlModal.tsx b/admin/inertia/components/AppUrlModal.tsx index 760a259d..75c6c853 100644 --- a/admin/inertia/components/AppUrlModal.tsx +++ b/admin/inertia/components/AppUrlModal.tsx @@ -79,6 +79,7 @@ export default function AppUrlModal({ open, service, onClose, onSaved, showError name="customUrl" label="Custom URL" placeholder="http://jellyfin.myhomelab.net" + maxLength={255} value={value} onChange={(e) => setValue(e.target.value)} error={isInvalid} diff --git a/admin/inertia/lib/navigation.ts b/admin/inertia/lib/navigation.ts index 88eea024..2a48aa21 100644 --- a/admin/inertia/lib/navigation.ts +++ b/admin/inertia/lib/navigation.ts @@ -13,7 +13,8 @@ export function normalizeCustomUrl(input: string | null | undefined): string | n try { const url = new URL(withScheme); if (url.protocol !== 'http:' && url.protocol !== 'https:') return null; - return url.href; + // Keep the normalized value within services.custom_url's VARCHAR(255) column. + return url.href.length <= 255 ? url.href : null; } catch { return null; } @@ -57,4 +58,4 @@ export function getServiceLink(ui_location: string, customUrl?: string | null): } return `/${ui_location}`; -} \ No newline at end of file +} diff --git a/admin/tests/unit/custom_url.spec.ts b/admin/tests/unit/custom_url.spec.ts new file mode 100644 index 00000000..2aa13b39 --- /dev/null +++ b/admin/tests/unit/custom_url.spec.ts @@ -0,0 +1,15 @@ +import * as assert from 'node:assert/strict' +import { test } from 'node:test' + +import { normalizeCustomUrl } from '../../app/validators/system.js' + +test('rejects a normalized custom URL that exceeds the database column size', () => { + const tooLongHost = 'a'.repeat(247) + assert.equal(normalizeCustomUrl(`https://${tooLongHost}`), null) +}) + +test('accepts a normalized custom URL at the database column limit', () => { + const maxLengthHost = 'a'.repeat(246) + const normalized = normalizeCustomUrl(`https://${maxLengthHost}`) + assert.equal(normalized?.length, 255) +})