This commit is contained in:
+37
-18
@@ -4,6 +4,17 @@ import crypto from "node:crypto";
|
||||
import { ParsedHashEntry } from "../shared/types";
|
||||
import { MAX_MANIFEST_FILE_BYTES } from "./constants";
|
||||
|
||||
const manifestCache = new Map<string, { at: number; entries: Map<string, ParsedHashEntry> }>();
|
||||
const MANIFEST_CACHE_TTL_MS = 15000;
|
||||
|
||||
function normalizeManifestKey(value: string): string {
|
||||
return String(value || "")
|
||||
.replace(/\\/g, "/")
|
||||
.replace(/^\.\//, "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function parseHashLine(line: string): ParsedHashEntry | null {
|
||||
const text = String(line || "").trim();
|
||||
if (!text || text.startsWith(";")) {
|
||||
@@ -30,6 +41,12 @@ export function parseHashLine(line: string): ParsedHashEntry | null {
|
||||
}
|
||||
|
||||
export function readHashManifest(packageDir: string): Map<string, ParsedHashEntry> {
|
||||
const cacheKey = path.resolve(packageDir);
|
||||
const cached = manifestCache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.at <= MANIFEST_CACHE_TTL_MS) {
|
||||
return new Map(cached.entries);
|
||||
}
|
||||
|
||||
const map = new Map<string, ParsedHashEntry>();
|
||||
const patterns: Array<[string, "crc32" | "md5" | "sha1"]> = [
|
||||
[".sfv", "crc32"],
|
||||
@@ -80,24 +97,28 @@ export function readHashManifest(packageDir: string): Map<string, ParsedHashEntr
|
||||
...parsed,
|
||||
algorithm: hit[1]
|
||||
};
|
||||
const key = parsed.fileName.toLowerCase();
|
||||
const key = normalizeManifestKey(parsed.fileName);
|
||||
if (map.has(key)) {
|
||||
continue;
|
||||
}
|
||||
map.set(key, normalized);
|
||||
}
|
||||
}
|
||||
manifestCache.set(cacheKey, { at: Date.now(), entries: new Map(map) });
|
||||
return map;
|
||||
}
|
||||
|
||||
const crcTable = new Int32Array(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
let c = i;
|
||||
for (let j = 0; j < 8; j++) c = c & 1 ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
||||
crcTable[i] = c;
|
||||
}
|
||||
|
||||
function crc32Buffer(data: Buffer, seed = 0): number {
|
||||
let crc = seed ^ -1;
|
||||
for (let i = 0; i < data.length; i += 1) {
|
||||
let c = (crc ^ data[i]) & 0xff;
|
||||
for (let j = 0; j < 8; j += 1) {
|
||||
c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
||||
}
|
||||
crc = (crc >>> 8) ^ c;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
crc = (crc >>> 8) ^ crcTable[(crc ^ data[i]) & 0xff];
|
||||
}
|
||||
return crc ^ -1;
|
||||
}
|
||||
@@ -105,15 +126,12 @@ function crc32Buffer(data: Buffer, seed = 0): number {
|
||||
async function hashFile(filePath: string, algorithm: "crc32" | "md5" | "sha1"): Promise<string> {
|
||||
if (algorithm === "crc32") {
|
||||
const stream = fs.createReadStream(filePath, { highWaterMark: 1024 * 1024 });
|
||||
return await new Promise<string>((resolve, reject) => {
|
||||
let crc = 0;
|
||||
stream.on("data", (chunk: string | Buffer) => {
|
||||
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
||||
crc = crc32Buffer(buffer, crc);
|
||||
});
|
||||
stream.on("error", reject);
|
||||
stream.on("end", () => resolve(((crc >>> 0).toString(16)).padStart(8, "0").toLowerCase()));
|
||||
});
|
||||
let crc = 0;
|
||||
for await (const chunk of stream) {
|
||||
crc = crc32Buffer(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), crc);
|
||||
await new Promise(r => setImmediate(r));
|
||||
}
|
||||
return (crc >>> 0).toString(16).padStart(8, "0").toLowerCase();
|
||||
}
|
||||
|
||||
const hash = crypto.createHash(algorithm);
|
||||
@@ -130,8 +148,9 @@ export async function validateFileAgainstManifest(filePath: string, packageDir:
|
||||
if (manifest.size === 0) {
|
||||
return { ok: true, message: "Kein Hash verfügbar" };
|
||||
}
|
||||
const key = path.basename(filePath).toLowerCase();
|
||||
const entry = manifest.get(key);
|
||||
const keyByBaseName = normalizeManifestKey(path.basename(filePath));
|
||||
const keyByRelativePath = normalizeManifestKey(path.relative(packageDir, filePath));
|
||||
const entry = manifest.get(keyByRelativePath) || manifest.get(keyByBaseName);
|
||||
if (!entry) {
|
||||
return { ok: true, message: "Kein Hash für Datei" };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user