Files
makelore/electron/api/works-play-url.ts

63 lines
1.7 KiB
TypeScript

const MAX_WORKS_PLAY_URL_LENGTH = 1_024;
function isLoopbackHostname(hostname: string): boolean {
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, '');
return normalized === 'localhost'
|| normalized.endsWith('.localhost')
|| normalized === '::'
|| normalized === '::1'
|| normalized.startsWith('::ffff:127.')
|| normalized.startsWith('::ffff:7f00:')
|| normalized === '0.0.0.0'
|| normalized.startsWith('127.');
}
function trustedWorksHttpsUrl(value: string, worksBase: URL): URL | null {
try {
const target = new URL(value, worksBase);
if (
target.toString().length > MAX_WORKS_PLAY_URL_LENGTH
|| worksBase.protocol !== 'https:'
|| target.protocol !== 'https:'
|| target.origin !== worksBase.origin
|| Boolean(target.username || target.password)
|| isLoopbackHostname(target.hostname)
) {
return null;
}
return target;
} catch {
return null;
}
}
export function trustedWorksProjectPlayUrl(
value: string,
worksBase: URL,
appId: string,
): string | null {
const normalizedAppId = appId.trim();
if (!normalizedAppId) return null;
const target = trustedWorksHttpsUrl(value, worksBase);
if (
!target
|| target.pathname !== `/apps/${encodeURIComponent(normalizedAppId)}/`
|| target.search
|| target.hash
) {
return null;
}
return target.toString();
}
export function trustedWorksReleasePreviewUrl(
value: string,
worksBase: URL,
releaseId: string,
): string | null {
const target = trustedWorksHttpsUrl(value, worksBase);
if (!target) return null;
const expectedPrefix = `/previews/${encodeURIComponent(releaseId)}/`;
return target.pathname.startsWith(expectedPrefix) ? target.toString() : null;
}