import { randomUUID } from "node:crypto"; import { constants as fsConstants } from "node:fs"; import type { Stats } from "node:fs"; import type { FileHandle } from "node:fs/promises"; import fs from "node:fs/promises"; import path from "node:path"; import { Readable, Transform } from "node:stream"; import { pipeline } from "node:stream/promises"; import JSZip from "jszip"; import * as tar from "tar"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import {
resolveArchiveOutputPath,
stripArchivePath,
validateArchiveEntryPath,
} from "./archive-path.js"; import {
createArchiveSymlinkTraversalError,
mergeExtractedTreeIntoDestination,
prepareArchiveDestinationDir,
prepareArchiveOutputPath,
withStagedArchiveDestination,
} from "./archive-staging.js"; import { sameFileIdentity } from "./file-identity.js"; import { openFileWithinRoot, openWritableFileWithinRoot, SafeOpenError } from "./fs-safe.js"; import { isNotFoundPathError } from "./path-guards.js";
export type ArchiveExtractLimits = { /** * Max archive file bytes (compressed).
*/
maxArchiveBytes?: number; /** Max number of extracted entries (files + dirs). */
maxEntries?: number; /** Max extracted bytes (sum of all files). */
maxExtractedBytes?: number; /** Max extracted bytes for a single file entry. */
maxEntryBytes?: number;
};
export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-staging.js";
export {
mergeExtractedTreeIntoDestination,
prepareArchiveDestinationDir,
prepareArchiveOutputPath,
withStagedArchiveDestination,
} from "./archive-staging.js";
export async function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
label: string,
): Promise<T> {
let timeoutId: ReturnType<typeof setTimeout> | undefined; try { return await Promise.race([
promise, new Promise<T>((_, reject) => {
timeoutId = setTimeout(
() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
timeoutMs,
);
}),
]);
} finally { if (timeoutId) {
clearTimeout(timeoutId);
}
}
}
type ResolvedArchiveExtractLimits = Required<ArchiveExtractLimits>;
function clampLimit(value: number | undefined): number | undefined { if (typeof value !== "number" || !Number.isFinite(value)) { return undefined;
} const v = Math.floor(value); return v > 0 ? v : undefined;
}
function resolveExtractLimits(limits?: ArchiveExtractLimits): ResolvedArchiveExtractLimits { // Defaults: defensive, but should not break normal installs. return {
maxArchiveBytes: clampLimit(limits?.maxArchiveBytes) ?? DEFAULT_MAX_ARCHIVE_BYTES_ZIP,
maxEntries: clampLimit(limits?.maxEntries) ?? DEFAULT_MAX_ENTRIES,
maxExtractedBytes: clampLimit(limits?.maxExtractedBytes) ?? DEFAULT_MAX_EXTRACTED_BYTES,
maxEntryBytes: clampLimit(limits?.maxEntryBytes) ?? DEFAULT_MAX_ENTRY_BYTES,
};
}
function assertArchiveEntryCountWithinLimit(
entryCount: number,
limits: ResolvedArchiveExtractLimits,
) { if (entryCount > limits.maxEntries) { thrownew Error(ERROR_ARCHIVE_ENTRY_COUNT_EXCEEDS_LIMIT);
}
}
function createByteBudgetTracker(limits: ResolvedArchiveExtractLimits): {
startEntry: () => void;
addBytes: (bytes: number) => void;
addEntrySize: (size: number) => void;
} {
let entryBytes = 0;
let extractedBytes = 0;
return {
startEntry() {
entryBytes = 0;
},
addBytes,
addEntrySize(size: number) { const s = Math.max(0, Math.floor(size)); if (s > limits.maxEntryBytes) { thrownew Error(ERROR_ARCHIVE_ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
} // Note: tar budgets are based on the header-declared size.
addBytes(s);
},
};
}
type ZipExtractBudget = ReturnType<typeof createByteBudgetTracker>;
async function readZipEntryStream(entry: ZipEntry): Promise<NodeJS.ReadableStream> { if (typeof entry.nodeStream === "function") { return entry.nodeStream();
} // Old JSZip: fall back to buffering, but still extract via a stream. const buf = await entry.async("nodebuffer"); return Readable.from(buf);
}
export async function readJsonFile<T>(filePath: string): Promise<T> { const raw = await fs.readFile(filePath, "utf-8"); return JSON.parse(raw) as T;
}
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.14Bemerkung:
(vorverarbeitet am 2026-06-09)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.