mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 20:56:19 -08:00
* docs: design Linux frame-copy packaging * docs: plan Linux frame-copy packaging * feat(packaging): define Linux frame-copy profiles * fix(packaging): reject inherited profile names * feat(embedded-mpv): validate staged Linux runtime * fix(embedded-mpv): require Linux source packages * fix(embedded-mpv): harden Linux runtime staging * feat(embedded-mpv): build LGPL Linux runtime * fix(embedded-mpv): pin Linux runtime inputs * feat(embedded-mpv): build relocatable Linux helper * fix(embedded-mpv): require bundled Linux runtime * fix(embedded-mpv): make Linux runtime portable * feat(packaging): ship Linux frame-copy artifacts * fix(embedded-mpv): verify Linux helper linkage * fix(packaging): enforce Linux frame-copy isolation * fix(embedded-mpv): pin Linux display data * docs(embedded-mpv): document Linux frame-copy packaging * feat(embedded-mpv): probe Linux frame-copy runtime * test(embedded-mpv): smoke packaged Linux frame-copy * docs(embedded-mpv): clarify Linux system runtime baseline * fix(embedded-mpv): harden Linux runtime capability gate * ci: verify Linux frame-copy packages * test(embedded-mpv): harden packaged Linux smoke * test(embedded-mpv): preserve packaged GL mode * test(packaging): harden Linux package probes * fix(embedded-mpv): enable private Snap shared memory * fix(embedded-mpv): sanitize Linux helper environment * fix(packaging): enforce private Snap memory semantics * fix(packaging): reject ambiguous Snap memory metadata * fix(embedded-mpv): prioritize trusted Snap GL * fix(packaging): reject advanced Snap YAML semantics * fix(packaging): reject arbitrary Snap YAML aliases * feat(packaging): ship Linux runtime license notices * docs(embedded-mpv): document Linux runtime distribution * fix(packaging): parse Snap trailing comments safely * fix(release): gate Snap publish on public source release * fix(packaging): strip VCS metadata from source bundle * docs(packaging): clarify Linux source release gate * test(embedded-mpv): smoke missing bundled libmpv * style(embedded-mpv): format final validation inputs * fix(e2e): satisfy fixture index signature typing * fix(ci): declare fontconfig gperf generator * fix(embedded-mpv): hash runtime cache identities * fix(packaging): harden Linux frame-copy delivery * fix(packaging): tighten runtime delivery gates * fix(ci): decouple Linux runtime matrix * fix(packaging): harden Linux frame-copy delivery * fix(packaging): validate Linux frame-copy runtimes * fix(packaging): scope Snap Electron library checks * feat(packaging): ship Linux frame-copy runtimes * fix(packaging): improve Linux runtime smoke diagnostics * fix(packaging): expose bounded helper probe details * test(packaging): trace Snap EGL probe failures * fix(packaging): prefer core22 ABI in Snap helper * fix(packaging): bound helper probe capture * fix(packaging): harden Linux frame-copy releases * fix(packaging): canonicalize libplacebo submodule identity * fix(packaging): make source archive inspection portable * fix(packaging): harden Snap release verification
163 lines
4.5 KiB
TypeScript
163 lines
4.5 KiB
TypeScript
import { createHash } from 'crypto';
|
|
import type * as nodeFileSystem from 'fs';
|
|
import path from 'path';
|
|
import { isDeepStrictEqual } from 'util';
|
|
import {
|
|
SAFE_RUNTIME_NAME_PATTERN,
|
|
SHA256_PATTERN,
|
|
SHARED_LIBRARY_PATTERN,
|
|
} from './contracts';
|
|
import type {
|
|
EmbeddedMpvFrameCopyRuntimeFailureReason,
|
|
EmbeddedMpvFrameCopyRuntimeFileSystem,
|
|
EmbeddedMpvFrameCopyRuntimeResult,
|
|
RuntimeFile,
|
|
ValidationFailure,
|
|
ValidationResult,
|
|
} from './types';
|
|
|
|
export function failure(
|
|
reason: EmbeddedMpvFrameCopyRuntimeFailureReason
|
|
): EmbeddedMpvFrameCopyRuntimeResult {
|
|
return { usable: false, reason };
|
|
}
|
|
|
|
export function validationFailure(
|
|
reason: EmbeddedMpvFrameCopyRuntimeFailureReason
|
|
): ValidationFailure {
|
|
return { reason };
|
|
}
|
|
|
|
export function isValidationFailure<T>(
|
|
result: ValidationResult<T>
|
|
): result is ValidationFailure {
|
|
return 'reason' in result;
|
|
}
|
|
|
|
export function isObject(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
}
|
|
|
|
export function hasExactFields(
|
|
value: Record<string, unknown>,
|
|
fields: readonly string[]
|
|
): boolean {
|
|
return isDeepStrictEqual(Object.keys(value).sort(), [...fields].sort());
|
|
}
|
|
|
|
export function isSafeRuntimeName(value: unknown): value is string {
|
|
return (
|
|
typeof value === 'string' &&
|
|
value.length > 0 &&
|
|
value !== '.' &&
|
|
value !== '..' &&
|
|
path.basename(value) === value &&
|
|
!value.includes('/') &&
|
|
!value.includes('\\') &&
|
|
SAFE_RUNTIME_NAME_PATTERN.test(value) &&
|
|
SHARED_LIBRARY_PATTERN.test(value)
|
|
);
|
|
}
|
|
|
|
export function isMissingFileError(error: unknown): boolean {
|
|
return (
|
|
isObject(error) &&
|
|
typeof error.code === 'string' &&
|
|
(error.code === 'ENOENT' || error.code === 'ENOTDIR')
|
|
);
|
|
}
|
|
|
|
export function fileIdentity(
|
|
filePath: string,
|
|
stat: nodeFileSystem.Stats,
|
|
contents: Buffer
|
|
): string {
|
|
return [
|
|
path.resolve(filePath),
|
|
stat.dev,
|
|
stat.ino,
|
|
stat.mode,
|
|
stat.size,
|
|
stat.mtimeMs,
|
|
stat.ctimeMs,
|
|
createHash('sha256').update(contents).digest('hex'),
|
|
].join(':');
|
|
}
|
|
|
|
export function readManifest(
|
|
manifestPath: string,
|
|
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem
|
|
): ValidationResult<Record<string, unknown>> {
|
|
let contents: Buffer;
|
|
try {
|
|
contents = fileSystem.readFileSync(manifestPath);
|
|
} catch {
|
|
return validationFailure('runtime-manifest-invalid');
|
|
}
|
|
|
|
try {
|
|
const parsed: unknown = JSON.parse(contents.toString('utf8'));
|
|
return isObject(parsed)
|
|
? { value: parsed }
|
|
: validationFailure('runtime-manifest-invalid');
|
|
} catch {
|
|
return validationFailure('runtime-manifest-invalid');
|
|
}
|
|
}
|
|
|
|
export function validateTargets(
|
|
targets: unknown,
|
|
allowedTargets: ReadonlySet<string>
|
|
): boolean {
|
|
const expectedTargets = [...allowedTargets].sort();
|
|
if (
|
|
!Array.isArray(targets) ||
|
|
targets.length !== expectedTargets.length ||
|
|
targets.some(
|
|
(target) =>
|
|
typeof target !== 'string' ||
|
|
target.trim() !== target ||
|
|
target.toLowerCase() !== target ||
|
|
!allowedTargets.has(target)
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
return isDeepStrictEqual(targets, expectedTargets);
|
|
}
|
|
|
|
export function validateRuntimeFiles(value: unknown): RuntimeFile[] | null {
|
|
if (!Array.isArray(value) || value.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const runtimeFiles: RuntimeFile[] = [];
|
|
const names = new Set<string>();
|
|
for (const candidate of value) {
|
|
if (
|
|
!isObject(candidate) ||
|
|
!hasExactFields(candidate, ['name', 'sha256', 'size']) ||
|
|
!isSafeRuntimeName(candidate.name) ||
|
|
!Number.isSafeInteger(candidate.size) ||
|
|
(candidate.size as number) <= 0 ||
|
|
typeof candidate.sha256 !== 'string' ||
|
|
!SHA256_PATTERN.test(candidate.sha256) ||
|
|
names.has(candidate.name)
|
|
) {
|
|
return null;
|
|
}
|
|
names.add(candidate.name);
|
|
runtimeFiles.push({
|
|
name: candidate.name,
|
|
size: candidate.size as number,
|
|
sha256: candidate.sha256,
|
|
});
|
|
}
|
|
return isDeepStrictEqual(
|
|
runtimeFiles.map(({ name }) => name).sort(),
|
|
runtimeFiles.map(({ name }) => name)
|
|
)
|
|
? runtimeFiles
|
|
: null;
|
|
}
|