Release Privacy Tools 0.1.0
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
encodeText,
|
||||
sanitizeDownloadFilename,
|
||||
stableStringify,
|
||||
} from "@add-ideas/toolbox-helpers";
|
||||
import { zipSync } from "fflate";
|
||||
|
||||
import { APP_VERSION } from "../version";
|
||||
import {
|
||||
assertLimit,
|
||||
DEFAULT_PRIVACY_LIMITS,
|
||||
PrivacyLimitError,
|
||||
} from "./limits";
|
||||
import type {
|
||||
BatchReport,
|
||||
ImageScanResult,
|
||||
SanitizedAsset,
|
||||
SanitizationReport,
|
||||
} from "./model";
|
||||
|
||||
export function createBatchReport(
|
||||
files: readonly ImageScanResult[],
|
||||
assets: readonly SanitizedAsset[],
|
||||
generatedAt = new Date().toISOString(),
|
||||
): BatchReport {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
generatedAt,
|
||||
application: { name: "Privacy Tools", version: APP_VERSION },
|
||||
files: [...files],
|
||||
sanitizations: assets.map((asset) => asset.report),
|
||||
warnings: [
|
||||
"This report may itself contain sensitive source metadata and filenames; store and share it deliberately.",
|
||||
"A successful re-scan is not an anonymity guarantee; see each sanitization disclaimer.",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function serializeReport(
|
||||
report: BatchReport | SanitizationReport,
|
||||
): string {
|
||||
return stableStringify(report, 2, {
|
||||
maxDepth: 64,
|
||||
maxNodes: 500_000,
|
||||
maxTextChars: 2_000_000,
|
||||
});
|
||||
}
|
||||
|
||||
export async function createBatchArchive(
|
||||
files: readonly ImageScanResult[],
|
||||
assets: readonly SanitizedAsset[],
|
||||
generatedAt = new Date().toISOString(),
|
||||
): Promise<Blob> {
|
||||
assertLimit(
|
||||
files.length,
|
||||
DEFAULT_PRIVACY_LIMITS.maxFiles,
|
||||
"Report file count",
|
||||
);
|
||||
assertLimit(
|
||||
assets.length,
|
||||
DEFAULT_PRIVACY_LIMITS.maxFiles,
|
||||
"Archive image count",
|
||||
);
|
||||
const entries: Record<string, Uint8Array> = Object.create(null) as Record<
|
||||
string,
|
||||
Uint8Array
|
||||
>;
|
||||
const names = new Set<string>();
|
||||
let total = 0;
|
||||
for (const asset of assets) {
|
||||
const name = uniqueName(asset.report.outputName, names);
|
||||
const bytes = new Uint8Array(await asset.blob.arrayBuffer());
|
||||
total += bytes.byteLength;
|
||||
if (total > DEFAULT_PRIVACY_LIMITS.maxZipBytes)
|
||||
throw new PrivacyLimitError(
|
||||
"Batch archive input size",
|
||||
total,
|
||||
DEFAULT_PRIVACY_LIMITS.maxZipBytes,
|
||||
);
|
||||
entries[`images/${name}`] = bytes;
|
||||
}
|
||||
const report = encodeText(
|
||||
serializeReport(createBatchReport(files, assets, generatedAt)),
|
||||
);
|
||||
total += report.byteLength;
|
||||
if (total > DEFAULT_PRIVACY_LIMITS.maxZipBytes)
|
||||
throw new PrivacyLimitError(
|
||||
"Batch archive input size",
|
||||
total,
|
||||
DEFAULT_PRIVACY_LIMITS.maxZipBytes,
|
||||
);
|
||||
entries["privacy-tools-report.json"] = report;
|
||||
const archive = zipSync(entries, {
|
||||
level: 0,
|
||||
mtime: new Date("1980-01-01T00:00:00.000Z"),
|
||||
});
|
||||
if (archive.byteLength > DEFAULT_PRIVACY_LIMITS.maxZipBytes)
|
||||
throw new PrivacyLimitError(
|
||||
"Batch archive output size",
|
||||
archive.byteLength,
|
||||
DEFAULT_PRIVACY_LIMITS.maxZipBytes,
|
||||
);
|
||||
const ownedArchive = archive.slice().buffer as ArrayBuffer;
|
||||
return new Blob([ownedArchive], { type: "application/zip" });
|
||||
}
|
||||
|
||||
function uniqueName(input: string, used: Set<string>): string {
|
||||
const safe = sanitizeDownloadFilename(input, "image.clean");
|
||||
if (!used.has(safe)) {
|
||||
used.add(safe);
|
||||
return safe;
|
||||
}
|
||||
const dot = safe.lastIndexOf(".");
|
||||
const stem = dot > 0 ? safe.slice(0, dot) : safe;
|
||||
const extension = dot > 0 ? safe.slice(dot) : "";
|
||||
let counter = 2;
|
||||
while (used.has(`${stem}-${counter}${extension}`)) counter += 1;
|
||||
const candidate = `${stem}-${counter}${extension}`;
|
||||
used.add(candidate);
|
||||
return candidate;
|
||||
}
|
||||
Reference in New Issue
Block a user