chore: 整理 third-party Dark Reader 目录并移除本地说明文件跟踪
This commit is contained in:
+231
@@ -0,0 +1,231 @@
|
||||
import {mkdir, rm, copyFile, writeFile, readFile, stat} from 'node:fs/promises';
|
||||
import {tmpdir} from 'node:os';
|
||||
|
||||
import unzipper from 'adm-zip';
|
||||
|
||||
import {log} from './utils.js';
|
||||
|
||||
const tmpDirParent = `${tmpdir()}/darkreader-integrity`;
|
||||
|
||||
function assert(claim) {
|
||||
if (!claim) {
|
||||
throw new Error('Assertion failed');
|
||||
}
|
||||
}
|
||||
|
||||
async function firefoxFetchAllReleases() {
|
||||
try {
|
||||
const file = await readFile(`${tmpDirParent}/firefox-index.json`);
|
||||
log.ok('Found previously stored index');
|
||||
return JSON.parse(file);
|
||||
} catch {
|
||||
log.ok('Fetching release URLs from Mozilla');
|
||||
}
|
||||
const versions = [];
|
||||
let dataUrl = 'https://addons.mozilla.org/api/v5/addons/addon/darkreader/versions/';
|
||||
while (dataUrl) {
|
||||
const {next, results} = await (await fetch(dataUrl)).json();
|
||||
dataUrl = next;
|
||||
for (const {file, version} of results) {
|
||||
const {hash, url, size} = file;
|
||||
versions.push({version, hash, url, size});
|
||||
}
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
function toBuffer(arrayBuffer) {
|
||||
const buffer = Buffer.alloc(arrayBuffer.byteLength);
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < buffer.length; ++i) {
|
||||
buffer[i] = view[i];
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function firefoxExtractHashMetaInfOrder(manifest) {
|
||||
function getDigestAlgos(lines) {
|
||||
const digestHeader = lines[3];
|
||||
if (digestHeader === 'Digest-Algorithms: MD5 SHA1') {
|
||||
return {
|
||||
type: 0,
|
||||
lineCount: 5,
|
||||
digestFormat: {
|
||||
digestHeader,
|
||||
digestLines: [
|
||||
'MD5-Digest: ',
|
||||
'SHA1-Digest: ',
|
||||
],
|
||||
digestLinesLengths: [36, 41],
|
||||
},
|
||||
};
|
||||
} else if (digestHeader === 'Digest-Algorithms: MD5 SHA1 SHA256') {
|
||||
return {
|
||||
type: 1,
|
||||
lineCount: 6,
|
||||
digestFormat: {
|
||||
digestHeader,
|
||||
digestLines: [
|
||||
'MD5-Digest: ',
|
||||
'SHA1-Digest: ',
|
||||
'SHA256-Digest: ',
|
||||
],
|
||||
digestLinesLengths: [36, 41, 59],
|
||||
},
|
||||
};
|
||||
} else if (digestHeader === 'Digest-Algorithms: SHA1 SHA256') {
|
||||
return {
|
||||
type: 2,
|
||||
lineCount: 5,
|
||||
digestFormat: {
|
||||
digestHeader,
|
||||
digestLines: [
|
||||
'SHA1-Digest: ',
|
||||
'SHA256-Digest: ',
|
||||
],
|
||||
digestLinesLengths: [41, 59],
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error('Unknown combination of digest algorithms');
|
||||
}
|
||||
|
||||
function getFileName(lines, fileIndex, lineCount, digestFormat) {
|
||||
const lineIndex = 2 + fileIndex * lineCount;
|
||||
assert(lines[lineIndex - 1] === '');
|
||||
assert(lines[lineIndex].startsWith('Name: '));
|
||||
assert(lines[lineIndex + 1] === digestFormat.digestHeader);
|
||||
for (let i = 0; i < digestFormat.digestLines.length; i++) {
|
||||
const line = lines[lineIndex + i + 2];
|
||||
assert(line.startsWith(digestFormat.digestLines[i]));
|
||||
assert(line.length === digestFormat.digestLinesLengths[i]);
|
||||
}
|
||||
const fileName = lines[lineIndex].substring('Name: '.length);
|
||||
return fileName;
|
||||
}
|
||||
|
||||
function getFileCount(lines, lineCount) {
|
||||
const count = (lines.length - 3) / lineCount;
|
||||
assert(Number.isInteger(count));
|
||||
return count;
|
||||
}
|
||||
|
||||
function isSorted(arr) {
|
||||
return arr.every((v, i, a) => !i || a[i - 1] <= v);
|
||||
}
|
||||
|
||||
const lines = manifest.split('\n');
|
||||
assert(lines[0] === 'Manifest-Version: 1.0');
|
||||
|
||||
const {type, lineCount, digestFormat} = getDigestAlgos(lines);
|
||||
const fileCount = getFileCount(lines, lineCount);
|
||||
|
||||
const realOrder = [];
|
||||
for (let i = 0; i < fileCount; i++) {
|
||||
const fileName = getFileName(lines, i, lineCount, digestFormat);
|
||||
if (fileName !== 'manifest.json' && fileName !== 'mozilla-recommendation.json' && !fileName.startsWith('META-INF/')) {
|
||||
realOrder.push(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSorted(realOrder)) {
|
||||
return {type};
|
||||
}
|
||||
|
||||
const sortedOrder = [...realOrder].sort();
|
||||
const order = [];
|
||||
for (let i = 0; i < realOrder.length; i++) {
|
||||
order.push(sortedOrder.indexOf(realOrder[i]));
|
||||
}
|
||||
|
||||
return {type, order};
|
||||
}
|
||||
|
||||
function getIndent(fileJSON) {
|
||||
return fileJSON.indexOf('"') - fileJSON.indexOf('\n') - 1;
|
||||
}
|
||||
|
||||
function getManifestJSONData(fileJSON) {
|
||||
const indent = getIndent(fileJSON);
|
||||
const settings = JSON.parse(fileJSON).browser_specific_settings ? 1 : undefined;
|
||||
if (indent !== 2 || settings !== undefined) {
|
||||
return {
|
||||
settings,
|
||||
indent: indent !== 2 ? indent : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function firefoxFetchAllMetadata(noCache = false) {
|
||||
if (noCache) {
|
||||
await rm(tmpDirParent, {force: true, recursive: true});
|
||||
}
|
||||
try {
|
||||
await mkdir(tmpDirParent, {recursive: true});
|
||||
} catch (e) {
|
||||
// No need to create already existing directory
|
||||
}
|
||||
|
||||
const versions = await firefoxFetchAllReleases();
|
||||
log.ok(`Fetched release URLs (${versions.length})`);
|
||||
await writeFile(`${tmpDirParent}/firefox-index.json`, JSON.stringify(versions, null, 2));
|
||||
|
||||
for (const {version, url, size} of versions) {
|
||||
const fileName = `${tmpDirParent}/firefox-${version}.xpi`;
|
||||
const dest = `./integrity/firefox/${version}`;
|
||||
const tempDest = `${tmpDirParent}/firefox-${version}`;
|
||||
|
||||
try {
|
||||
const st = await stat(fileName);
|
||||
// Fast-fail path, it is actually never taken in practice
|
||||
if (st.size !== size) {
|
||||
throw new Error('Stored file had changed');
|
||||
}
|
||||
log.ok(`Found release file (Firefox, ${version})`);
|
||||
} catch {
|
||||
log.ok(`Fetching release file (${version})`);
|
||||
const file = await fetch(url);
|
||||
await writeFile(fileName, toBuffer(await file.arrayBuffer()));
|
||||
log.ok(`Wrote release file (${version})`);
|
||||
}
|
||||
|
||||
await rm(tempDest, {force: true, recursive: true});
|
||||
await mkdir(tempDest, {recursive: true});
|
||||
await rm(dest, {force: true, recursive: true});
|
||||
await mkdir(dest, {recursive: true});
|
||||
|
||||
const zip = new unzipper(fileName);
|
||||
zip.extractAllTo(tempDest);
|
||||
|
||||
|
||||
const manifestMf = await readFile(`${tempDest}/META-INF/manifest.mf`, {encoding: 'utf-8'});
|
||||
const {type, order} = firefoxExtractHashMetaInfOrder(manifestMf);
|
||||
|
||||
const manifestJSON = await readFile(`${tempDest}/manifest.json`, {encoding: 'utf-8'});
|
||||
const manifest = getManifestJSONData(manifestJSON);
|
||||
|
||||
const info = {
|
||||
type,
|
||||
manifest,
|
||||
order,
|
||||
};
|
||||
await writeFile(`${dest}/info.json`, `${JSON.stringify(info)}\n`);
|
||||
await copyFile(`${tempDest}/META-INF/mozilla.rsa`, `${dest}/mozilla.rsa`);
|
||||
try {
|
||||
await copyFile(`${tempDest}/META-INF/cose.sig`, `${dest}/cose.sig`);
|
||||
} catch (e) {
|
||||
// Nothing
|
||||
}
|
||||
try {
|
||||
await copyFile(`${tempDest}/mozilla-recommendation.json`, `${dest}/mozilla-recommendation.json`);
|
||||
} catch (e) {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main(noCache = false) {
|
||||
await firefoxFetchAllMetadata(noCache);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user