Files

63 lines
2.0 KiB
TypeScript

import path from 'path';
export const LEGACY_LOG_OUTPUT_REF_PREFIX = 'legacy-log-v1.';
export const MAX_LEGACY_LOG_PATH_BYTES = 360;
export const MAX_LEGACY_LOG_OUTPUT_REF_BYTES = 512;
function normalizeRelativeLogPath(value: string): string {
if (
!value ||
value.includes('\0') ||
value.includes('\\') ||
path.posix.isAbsolute(value) ||
path.win32.isAbsolute(value) ||
Buffer.byteLength(value, 'utf8') > MAX_LEGACY_LOG_PATH_BYTES
) {
throw new Error('Legacy log path must be a bounded relative POSIX path');
}
const normalized = path.posix.normalize(value);
if (
normalized === '.' ||
normalized === '..' ||
normalized.startsWith('../') ||
normalized.split('/').includes('..')
) {
throw new Error('Legacy log path escapes its configured root');
}
return normalized;
}
export function createLegacyLogOutputRef(logPath: string): string {
const normalized = normalizeRelativeLogPath(logPath);
const outputRef = `${LEGACY_LOG_OUTPUT_REF_PREFIX}${Buffer.from(
normalized,
'utf8',
).toString('base64url')}`;
if (Buffer.byteLength(outputRef, 'utf8') > MAX_LEGACY_LOG_OUTPUT_REF_BYTES) {
throw new Error('Legacy log output reference exceeds its size limit');
}
return outputRef;
}
export function parseLegacyLogOutputRef(outputRef?: string): string | null {
if (
!outputRef ||
!outputRef.startsWith(LEGACY_LOG_OUTPUT_REF_PREFIX) ||
Buffer.byteLength(outputRef, 'utf8') > MAX_LEGACY_LOG_OUTPUT_REF_BYTES
) {
return null;
}
const encoded = outputRef.slice(LEGACY_LOG_OUTPUT_REF_PREFIX.length);
if (!encoded || !/^[A-Za-z0-9_-]+$/.test(encoded)) return null;
try {
const bytes = Buffer.from(encoded, 'base64url');
if (bytes.toString('base64url') !== encoded) return null;
const decoded = bytes.toString('utf8');
if (Buffer.from(decoded, 'utf8').compare(bytes) !== 0) return null;
const normalized = normalizeRelativeLogPath(decoded);
return normalized === decoded ? decoded : null;
} catch {
return null;
}
}