mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-19 17:37:16 +02:00
chore: linting
This commit is contained in:
+24
-11
@@ -60,8 +60,12 @@ export function requestedTarget() {
|
|||||||
return match[1].trim();
|
return match[1].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sha256(path) {
|
function sha256(bytes) {
|
||||||
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
return createHash("sha256").update(bytes).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha256File(path) {
|
||||||
|
return sha256(readFileSync(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function xrayBinaryName(target) {
|
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.
|
/// on every attempt, so a truncated or substituted archive still cannot pass.
|
||||||
const DOWNLOAD_ATTEMPTS = 3;
|
const DOWNLOAD_ATTEMPTS = 3;
|
||||||
|
|
||||||
async function downloadVerifiedArchive(url, archive, expectedSha256) {
|
export async function downloadVerifiedArchive(url, archive, expectedSha256) {
|
||||||
let lastError;
|
let lastError;
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= DOWNLOAD_ATTEMPTS; attempt += 1) {
|
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})`,
|
`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) {
|
if (actual !== expectedSha256) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Xray-core checksum mismatch: expected ${expectedSha256}, got ${actual}`,
|
`Xray-core checksum mismatch: expected ${expectedSha256}, got ${actual}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
writeFileSync(archive, payload);
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = error;
|
lastError = error;
|
||||||
@@ -162,10 +172,13 @@ async function downloadVerifiedArchive(url, archive, expectedSha256) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadXray(target = requestedTarget()) {
|
export async function downloadXray(target = requestedTarget()) {
|
||||||
const asset = XRAY_ASSETS[target];
|
// `target` comes from --target/$TARGET, and it decides the file this writes
|
||||||
if (!asset) {
|
// 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}'`);
|
throw new Error(`Xray-core is not packaged for Rust target '${target}'`);
|
||||||
}
|
}
|
||||||
|
const asset = XRAY_ASSETS[target];
|
||||||
|
|
||||||
const windowsTarget = target.includes("windows");
|
const windowsTarget = target.includes("windows");
|
||||||
const destinationDir = join(MANIFEST_DIR, "binaries");
|
const destinationDir = join(MANIFEST_DIR, "binaries");
|
||||||
@@ -183,8 +196,8 @@ export async function downloadXray(target = requestedTarget()) {
|
|||||||
if (
|
if (
|
||||||
source.version === XRAY_VERSION &&
|
source.version === XRAY_VERSION &&
|
||||||
source.archiveSha256 === asset.sha256 &&
|
source.archiveSha256 === asset.sha256 &&
|
||||||
source.binarySha256 === sha256(destination) &&
|
source.binarySha256 === sha256File(destination) &&
|
||||||
source.licenseSha256 === sha256(licenseDestination)
|
source.licenseSha256 === sha256File(licenseDestination)
|
||||||
) {
|
) {
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
@@ -220,8 +233,8 @@ export async function downloadXray(target = requestedTarget()) {
|
|||||||
{
|
{
|
||||||
version: XRAY_VERSION,
|
version: XRAY_VERSION,
|
||||||
archiveSha256: asset.sha256,
|
archiveSha256: asset.sha256,
|
||||||
binarySha256: sha256(destination),
|
binarySha256: sha256File(destination),
|
||||||
licenseSha256: sha256(licenseDestination),
|
licenseSha256: sha256File(licenseDestination),
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import assert from "node:assert/strict";
|
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 { readFile } from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import {
|
import {
|
||||||
|
downloadVerifiedArchive,
|
||||||
downloadXray,
|
downloadXray,
|
||||||
windowsExtractionInvocation,
|
windowsExtractionInvocation,
|
||||||
XRAY_ASSETS,
|
XRAY_ASSETS,
|
||||||
@@ -122,3 +127,58 @@ test("rejects an unsupported target before downloading", async () => {
|
|||||||
/not packaged for Rust target/,
|
/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