mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -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
239 lines
7.0 KiB
TypeScript
239 lines
7.0 KiB
TypeScript
import { createHash } from 'crypto';
|
|
import * as nodeFileSystem from 'fs';
|
|
import path from 'path';
|
|
import {
|
|
FRAME_COPY_ADDON_NAME,
|
|
FRAME_COPY_HELPER_NAME,
|
|
FRAME_COPY_READER_NAME,
|
|
} from './contracts';
|
|
import {
|
|
validateDevelopmentManifest,
|
|
validatePackagedManifest,
|
|
} from './manifest-validator';
|
|
import type {
|
|
EmbeddedMpvFrameCopyManifestContract,
|
|
EmbeddedMpvFrameCopyRuntimeFileSystem,
|
|
RuntimeFile,
|
|
ValidatedPackage,
|
|
ValidationResult,
|
|
} from './types';
|
|
import {
|
|
isMissingFileError,
|
|
isValidationFailure,
|
|
readManifest,
|
|
validationFailure,
|
|
} from './validation-primitives';
|
|
|
|
function validateRegularArtifact(
|
|
filePath: string,
|
|
accessMode: number,
|
|
expectedMode: number | null,
|
|
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem
|
|
): ValidationResult<nodeFileSystem.Stats> {
|
|
let stat: nodeFileSystem.Stats;
|
|
try {
|
|
stat = fileSystem.lstatSync(filePath);
|
|
} catch (error) {
|
|
return validationFailure(
|
|
isMissingFileError(error)
|
|
? 'runtime-artifact-missing'
|
|
: 'runtime-artifact-invalid'
|
|
);
|
|
}
|
|
|
|
if (
|
|
stat.isSymbolicLink() ||
|
|
!stat.isFile() ||
|
|
(expectedMode !== null && (stat.mode & 0o7777) !== expectedMode)
|
|
) {
|
|
return validationFailure('runtime-artifact-invalid');
|
|
}
|
|
try {
|
|
fileSystem.accessSync(filePath, accessMode);
|
|
} catch {
|
|
return validationFailure('runtime-artifact-invalid');
|
|
}
|
|
return { value: stat };
|
|
}
|
|
|
|
function pathExistsByLstat(
|
|
filePath: string,
|
|
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem
|
|
): boolean {
|
|
try {
|
|
fileSystem.lstatSync(filePath);
|
|
return true;
|
|
} catch (error) {
|
|
if (isMissingFileError(error)) {
|
|
return false;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function validateBundledRuntimeFiles(
|
|
nativeDir: string,
|
|
runtimeFiles: RuntimeFile[],
|
|
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem
|
|
): ValidationResult<true> {
|
|
const libDir = path.join(nativeDir, 'lib');
|
|
let libStat: nodeFileSystem.Stats;
|
|
try {
|
|
libStat = fileSystem.lstatSync(libDir);
|
|
} catch {
|
|
return validationFailure('runtime-library-directory-invalid');
|
|
}
|
|
if (libStat.isSymbolicLink() || !libStat.isDirectory()) {
|
|
return validationFailure('runtime-library-directory-invalid');
|
|
}
|
|
|
|
let packagedNames: string[];
|
|
try {
|
|
packagedNames = fileSystem.readdirSync(libDir) as string[];
|
|
} catch {
|
|
return validationFailure('runtime-library-directory-invalid');
|
|
}
|
|
const declaredNames = new Set(runtimeFiles.map(({ name }) => name));
|
|
if (packagedNames.some((name) => !declaredNames.has(name))) {
|
|
return validationFailure('runtime-library-undeclared');
|
|
}
|
|
|
|
for (const runtimeFile of runtimeFiles) {
|
|
const runtimePath = path.join(libDir, runtimeFile.name);
|
|
let stat: nodeFileSystem.Stats;
|
|
try {
|
|
stat = fileSystem.lstatSync(runtimePath);
|
|
} catch (error) {
|
|
return validationFailure(
|
|
isMissingFileError(error)
|
|
? 'runtime-library-missing'
|
|
: 'runtime-library-invalid'
|
|
);
|
|
}
|
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
return validationFailure('runtime-library-invalid');
|
|
}
|
|
try {
|
|
fileSystem.accessSync(runtimePath, nodeFileSystem.constants.R_OK);
|
|
} catch {
|
|
return validationFailure('runtime-library-invalid');
|
|
}
|
|
if (stat.size !== runtimeFile.size) {
|
|
return validationFailure('runtime-library-size-mismatch');
|
|
}
|
|
|
|
let contents: Buffer;
|
|
try {
|
|
contents = fileSystem.readFileSync(runtimePath);
|
|
} catch {
|
|
return validationFailure('runtime-library-invalid');
|
|
}
|
|
if (contents.length !== runtimeFile.size) {
|
|
return validationFailure('runtime-library-size-mismatch');
|
|
}
|
|
const actualSha256 = createHash('sha256')
|
|
.update(contents)
|
|
.digest('hex');
|
|
if (actualSha256 !== runtimeFile.sha256) {
|
|
return validationFailure('runtime-library-hash-mismatch');
|
|
}
|
|
}
|
|
return { value: true };
|
|
}
|
|
|
|
export function validatePackage(
|
|
helperPath: string,
|
|
manifestPath: string,
|
|
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem,
|
|
manifestContract: EmbeddedMpvFrameCopyManifestContract
|
|
): ValidationResult<ValidatedPackage> {
|
|
const nativeDir = path.dirname(helperPath);
|
|
if (
|
|
path.basename(helperPath) !== FRAME_COPY_HELPER_NAME ||
|
|
path.dirname(manifestPath) !== nativeDir
|
|
) {
|
|
return validationFailure('runtime-artifact-invalid');
|
|
}
|
|
|
|
const manifestArtifact = validateRegularArtifact(
|
|
manifestPath,
|
|
nodeFileSystem.constants.R_OK,
|
|
0o644,
|
|
fileSystem
|
|
);
|
|
if (isValidationFailure(manifestArtifact)) {
|
|
return validationFailure(
|
|
manifestArtifact.reason === 'runtime-artifact-missing'
|
|
? 'runtime-manifest-missing'
|
|
: 'runtime-manifest-invalid'
|
|
);
|
|
}
|
|
const manifestResult = readManifest(manifestPath, fileSystem);
|
|
if (isValidationFailure(manifestResult)) {
|
|
return manifestResult;
|
|
}
|
|
const validManifest =
|
|
manifestContract === 'packaged'
|
|
? validatePackagedManifest(manifestResult.value)
|
|
: validateDevelopmentManifest(manifestResult.value);
|
|
if (isValidationFailure(validManifest)) {
|
|
return validManifest;
|
|
}
|
|
|
|
for (const [artifactPath, accessMode, expectedMode] of [
|
|
[
|
|
path.join(nativeDir, FRAME_COPY_ADDON_NAME),
|
|
nodeFileSystem.constants.R_OK,
|
|
null,
|
|
],
|
|
[
|
|
path.join(nativeDir, FRAME_COPY_READER_NAME),
|
|
nodeFileSystem.constants.R_OK,
|
|
0o644,
|
|
],
|
|
[
|
|
helperPath,
|
|
nodeFileSystem.constants.R_OK | nodeFileSystem.constants.X_OK,
|
|
0o755,
|
|
],
|
|
] as const) {
|
|
const artifact = validateRegularArtifact(
|
|
artifactPath,
|
|
accessMode,
|
|
expectedMode,
|
|
fileSystem
|
|
);
|
|
if (isValidationFailure(artifact)) {
|
|
return artifact;
|
|
}
|
|
}
|
|
|
|
const libDir = path.join(nativeDir, 'lib');
|
|
if (validManifest.value.runtimeMode === 'system') {
|
|
try {
|
|
if (pathExistsByLstat(libDir, fileSystem)) {
|
|
return validationFailure('runtime-library-directory-invalid');
|
|
}
|
|
} catch {
|
|
return validationFailure('runtime-library-directory-invalid');
|
|
}
|
|
} else {
|
|
const bundledRuntime = validateBundledRuntimeFiles(
|
|
nativeDir,
|
|
validManifest.value.runtimeFiles,
|
|
fileSystem
|
|
);
|
|
if (isValidationFailure(bundledRuntime)) {
|
|
return bundledRuntime;
|
|
}
|
|
}
|
|
|
|
return {
|
|
value: {
|
|
manifest: validManifest.value,
|
|
helperPath,
|
|
nativeDir,
|
|
},
|
|
};
|
|
}
|