63 lines
1.7 KiB
TypeScript
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;
|
|
}
|