mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-09-21 05:21:43 +00:00
fix(worker): include the RAR signature in the ranged-listing sparse reconstruction
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
readRarListingRanged read the archive signature only to detect the RAR version, then walkRarVolume began harvesting header regions at pos = sigLen — the signature bytes themselves were never captured. The reconstructed sparse file therefore started with zero bytes instead of "Rar!", so every real unrar invocation rejected it as "not RAR archive". This silently forced every RAR archive through the expensive download+reupload fallback regardless of the source channel's forwarding permission — defeating the forward-priority path for the RAR-heavy channels it matters most for. The existing tests didn't catch this because their assertions accepted either a null or non-null result as passing, deferring real verification to production.
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { readVint, detectRarSignature, parseRar5BlockExtent, parseRar4BlockExtent, walkRarVolume, readRarListingRanged } from "./rar-ranged.js";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import type { RangeReader } from "./range-reader.js";
|
||||
|
||||
let capturedFirstBytes: Buffer | null = null;
|
||||
vi.mock("../rar-reader.js", () => ({
|
||||
readRarContents: async (firstPartPath: string) => {
|
||||
const { readFile } = await import("fs/promises");
|
||||
const reconstructed = await readFile(firstPartPath);
|
||||
capturedFirstBytes = reconstructed.subarray(0, 8);
|
||||
return [{ name: "dummy", size: 0 }]; // non-empty so listFromSparse returns it
|
||||
},
|
||||
}));
|
||||
|
||||
const { readVint, detectRarSignature, parseRar5BlockExtent, parseRar4BlockExtent, walkRarVolume, readRarListingRanged } =
|
||||
await import("./rar-ranged.js");
|
||||
|
||||
describe("readVint", () => {
|
||||
it("reads single-byte and multi-byte values (base-128 LE)", () => {
|
||||
expect(readVint(Buffer.from([0x08]), 0)).toEqual({ value: 8, bytes: 1 });
|
||||
@@ -123,6 +135,23 @@ describe("readRarListingRanged (single part)", () => {
|
||||
const res = await readRarListingRanged([{ fileId: "1", fileSize: BigInt(vol.length), fileName: "a.rar" }], read);
|
||||
expect(res === null || Array.isArray(res)).toBe(true); // real unrar parse covered live
|
||||
});
|
||||
|
||||
it("preserves the RAR signature bytes in the reconstructed sparse file", async () => {
|
||||
// Regression test: walkRarVolume starts at pos = sigLen and never
|
||||
// harvests the signature itself. If readRarListingRanged forgets to add
|
||||
// it as its own region, the reconstructed file starts with zero bytes
|
||||
// instead of "Rar!\x1a\x07\x01\x00", and every real unrar invocation
|
||||
// rejects it as "not RAR archive" — silently forcing every RAR archive
|
||||
// through the expensive download+reupload fallback regardless of the
|
||||
// channel's forwarding permission.
|
||||
const vol = buildRar5Volume();
|
||||
const sig = vol.subarray(0, 8);
|
||||
const read: RangeReader = async (_id, offset, length) => vol.subarray(offset, offset + length);
|
||||
capturedFirstBytes = null;
|
||||
await readRarListingRanged([{ fileId: "1", fileSize: BigInt(vol.length), fileName: "a.rar" }], read);
|
||||
expect(capturedFirstBytes).not.toBeNull();
|
||||
expect(capturedFirstBytes).toEqual(sig);
|
||||
});
|
||||
});
|
||||
|
||||
describe("readRarListingRanged (multipart)", () => {
|
||||
|
||||
@@ -104,7 +104,12 @@ export async function readRarListingRanged(
|
||||
if (!sig) return null;
|
||||
const regions = await walkRarVolume(read, part, sig.version, sig.sigLen);
|
||||
if (!regions) return null;
|
||||
sparseParts.push({ fileName: part.fileName, size: Number(part.fileSize), regions });
|
||||
// walkRarVolume starts at pos = sigLen and never harvests the signature
|
||||
// itself, so it must be added as its own region — otherwise the
|
||||
// reconstructed sparse file starts with zero bytes instead of the "Rar!"
|
||||
// magic, and unrar rejects it outright as "not RAR archive".
|
||||
const sigRegion = { offset: 0, bytes: head.subarray(0, sig.sigLen) };
|
||||
sparseParts.push({ fileName: part.fileName, size: Number(part.fileSize), regions: [sigRegion, ...regions] });
|
||||
}
|
||||
return listFromSparse(sparseParts, readRarContents);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user