From 9efac0a60aab079b65fefb8a486f38743d96be76 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 10:37:25 +0000 Subject: [PATCH] refactor(cuesheet): images are referenced by link only A path to a local file resolves on the machine running ontime, but not for the clients we serve the cuesheet to, so we no longer accept it. Validation now parses the value as a URL and checks the protocol, which also rejects malformed values that the previous prefix check let through. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CKXcDrZoQXbJpaXiLi1aff --- .../views/cuesheet/__tests__/EditableImage.test.ts | 12 +++++++----- .../cuesheet-table-elements/EditableImage.tsx | 13 +++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/client/src/views/cuesheet/__tests__/EditableImage.test.ts b/apps/client/src/views/cuesheet/__tests__/EditableImage.test.ts index 0a2f6ad4f..7b971dd5f 100644 --- a/apps/client/src/views/cuesheet/__tests__/EditableImage.test.ts +++ b/apps/client/src/views/cuesheet/__tests__/EditableImage.test.ts @@ -3,20 +3,22 @@ import { describe, expect, test } from 'vitest'; import { isValidImageSource } from '../cuesheet-table/cuesheet-table-elements/EditableImage'; describe('isValidImageSource()', () => { - test('accepts images hosted elsewhere', () => { + test('accepts links to a hosted image', () => { expect(isValidImageSource('https://example.com/image.png')).toBe(true); expect(isValidImageSource('http://example.com/image.png')).toBe(true); }); - test('accepts images served by ontime', () => { - expect(isValidImageSource('/user/image.png')).toBe(true); - expect(isValidImageSource('/external/image.png')).toBe(true); + test('rejects references to a local file, they would not resolve for our clients', () => { + expect(isValidImageSource('/user/image.png')).toBe(false); + expect(isValidImageSource('user/image.png')).toBe(false); + expect(isValidImageSource('file:///Users/me/image.png')).toBe(false); + expect(isValidImageSource('C:\\images\\image.png')).toBe(false); }); test('rejects values which would not resolve to an image', () => { expect(isValidImageSource('www.example.com/image.png')).toBe(false); expect(isValidImageSource('example.com/image.png')).toBe(false); - expect(isValidImageSource('user/image.png')).toBe(false); + expect(isValidImageSource('https://')).toBe(false); expect(isValidImageSource('some text')).toBe(false); }); }); diff --git a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx index bb7a3b28d..fe258fe0e 100644 --- a/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx +++ b/apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx @@ -16,11 +16,16 @@ interface EditableImageProps { export default memo(EditableImage); /** - * An image is either hosted somewhere else - * or served by ontime itself (eg. a file placed in the user folder) + * Images are referenced by link: anything local to the machine running ontime + * would not resolve for the clients we serve the cuesheet to */ export function isValidImageSource(value: string): boolean { - return value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/'); + try { + const url = new URL(value); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch { + return false; + } } function EditableImage({ initialValue, fieldLabel, readOnly, updateValue }: EditableImageProps) { @@ -73,7 +78,7 @@ function EditableImage({ initialValue, fieldLabel, readOnly, updateValue }: Edit } }} /> - {isRejected && Use a link (https://...) or a file in ontime (/user/...)} + {isRejected && Images are referenced by link (https://...)} ); }