mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-10 21:20:24 +02:00
chore: linting
This commit is contained in:
+24
-11
@@ -60,8 +60,12 @@ export function requestedTarget() {
|
||||
return match[1].trim();
|
||||
}
|
||||
|
||||
function sha256(path) {
|
||||
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
||||
function sha256(bytes) {
|
||||
return createHash("sha256").update(bytes).digest("hex");
|
||||
}
|
||||
|
||||
function sha256File(path) {
|
||||
return sha256(readFileSync(path));
|
||||
}
|
||||
|
||||
export function xrayBinaryName(target) {
|
||||
@@ -127,7 +131,7 @@ function extractArchive(archive, destinationDir, windowsTarget) {
|
||||
/// on every attempt, so a truncated or substituted archive still cannot pass.
|
||||
const DOWNLOAD_ATTEMPTS = 3;
|
||||
|
||||
async function downloadVerifiedArchive(url, archive, expectedSha256) {
|
||||
export async function downloadVerifiedArchive(url, archive, expectedSha256) {
|
||||
let lastError;
|
||||
|
||||
for (let attempt = 1; attempt <= DOWNLOAD_ATTEMPTS; attempt += 1) {
|
||||
@@ -138,14 +142,20 @@ async function downloadVerifiedArchive(url, archive, expectedSha256) {
|
||||
`Failed to download Xray-core (${response.status} ${response.statusText})`,
|
||||
);
|
||||
}
|
||||
writeFileSync(archive, Buffer.from(await response.arrayBuffer()));
|
||||
|
||||
const actual = sha256(archive);
|
||||
// The response body is verified in memory and only then written out, so
|
||||
// bytes that fail the pinned digest never reach the file system at all.
|
||||
// Writing first and checking afterwards left an unverified archive on
|
||||
// disk for the rest of the attempt, and any later reader of that path
|
||||
// would have been trusting a plain network download.
|
||||
const payload = Buffer.from(await response.arrayBuffer());
|
||||
const actual = sha256(payload);
|
||||
if (actual !== expectedSha256) {
|
||||
throw new Error(
|
||||
`Xray-core checksum mismatch: expected ${expectedSha256}, got ${actual}`,
|
||||
);
|
||||
}
|
||||
writeFileSync(archive, payload);
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
@@ -162,10 +172,13 @@ async function downloadVerifiedArchive(url, archive, expectedSha256) {
|
||||
}
|
||||
|
||||
export async function downloadXray(target = requestedTarget()) {
|
||||
const asset = XRAY_ASSETS[target];
|
||||
if (!asset) {
|
||||
// `target` comes from --target/$TARGET, and it decides the file this writes
|
||||
// into src-tauri/binaries. Only an own key of the pinned table is a target;
|
||||
// a plain lookup also answers for inherited names like `constructor`.
|
||||
if (!Object.hasOwn(XRAY_ASSETS, target)) {
|
||||
throw new Error(`Xray-core is not packaged for Rust target '${target}'`);
|
||||
}
|
||||
const asset = XRAY_ASSETS[target];
|
||||
|
||||
const windowsTarget = target.includes("windows");
|
||||
const destinationDir = join(MANIFEST_DIR, "binaries");
|
||||
@@ -183,8 +196,8 @@ export async function downloadXray(target = requestedTarget()) {
|
||||
if (
|
||||
source.version === XRAY_VERSION &&
|
||||
source.archiveSha256 === asset.sha256 &&
|
||||
source.binarySha256 === sha256(destination) &&
|
||||
source.licenseSha256 === sha256(licenseDestination)
|
||||
source.binarySha256 === sha256File(destination) &&
|
||||
source.licenseSha256 === sha256File(licenseDestination)
|
||||
) {
|
||||
return destination;
|
||||
}
|
||||
@@ -220,8 +233,8 @@ export async function downloadXray(target = requestedTarget()) {
|
||||
{
|
||||
version: XRAY_VERSION,
|
||||
archiveSha256: asset.sha256,
|
||||
binarySha256: sha256(destination),
|
||||
licenseSha256: sha256(licenseDestination),
|
||||
binarySha256: sha256File(destination),
|
||||
licenseSha256: sha256File(licenseDestination),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
import {
|
||||
downloadVerifiedArchive,
|
||||
downloadXray,
|
||||
windowsExtractionInvocation,
|
||||
XRAY_ASSETS,
|
||||
@@ -122,3 +127,58 @@ test("rejects an unsupported target before downloading", async () => {
|
||||
/not packaged for Rust target/,
|
||||
);
|
||||
});
|
||||
|
||||
// `constructor`, `__proto__` and friends answer a plain `XRAY_ASSETS[target]`
|
||||
// lookup, and `target` picks the path this script writes into src-tauri.
|
||||
test("rejects inherited object keys as targets", async () => {
|
||||
for (const target of ["__proto__", "constructor", "toString"]) {
|
||||
await assert.rejects(
|
||||
downloadXray(target),
|
||||
/not packaged for Rust target/,
|
||||
target,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
async function withStubbedFetch(body, run) {
|
||||
const scratch = mkdtempSync(join(tmpdir(), "donut-xray-test-"));
|
||||
const archive = join(scratch, "Xray-linux-64.zip");
|
||||
const realFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => new Response(body);
|
||||
try {
|
||||
await run(archive);
|
||||
} finally {
|
||||
globalThis.fetch = realFetch;
|
||||
rmSync(scratch, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test("writes the archive once the pinned digest matches", async () => {
|
||||
const body = Buffer.from("xray archive bytes");
|
||||
const digest = createHash("sha256").update(body).digest("hex");
|
||||
|
||||
await withStubbedFetch(body, async (archive) => {
|
||||
await downloadVerifiedArchive(
|
||||
"https://example.invalid/x.zip",
|
||||
archive,
|
||||
digest,
|
||||
);
|
||||
assert.deepEqual(readFileSync(archive), body);
|
||||
});
|
||||
});
|
||||
|
||||
// The bytes are hashed in memory and only then written, so a substituted or
|
||||
// truncated response never lands on disk for a later step to pick up.
|
||||
test("leaves nothing on disk when the payload fails its checksum", async () => {
|
||||
await withStubbedFetch(Buffer.from("tampered"), async (archive) => {
|
||||
await assert.rejects(
|
||||
downloadVerifiedArchive(
|
||||
"https://example.invalid/x.zip",
|
||||
archive,
|
||||
"0".repeat(64),
|
||||
),
|
||||
/checksum mismatch/,
|
||||
);
|
||||
assert.equal(existsSync(archive), false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user