自动同步: 2026-07-01 02:10

This commit is contained in:
2026-07-01 02:10:07 +08:00
parent d20bd4cfab
commit 489a0835e9
4162 changed files with 1010609 additions and 12590 deletions
+127
View File
@@ -0,0 +1,127 @@
import { gunzipSync } from "fflate";
const TAR_BLOCK_SIZE = 512;
function isRecord(value) {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function textFromBytes(bytes) {
return new TextDecoder().decode(bytes);
}
function readTarString(block, offset, length) {
const slice = block.subarray(offset, offset + length);
const end = slice.indexOf(0);
return textFromBytes(end === -1 ? slice : slice.subarray(0, end)).trim();
}
function readTarSize(block) {
const raw = readTarString(block, 124, 12).split("\0").join("").trim();
if (!raw)
return 0;
const size = Number.parseInt(raw, 8);
if (!Number.isFinite(size) || size < 0)
throw new Error("Invalid tar entry size");
return size;
}
function normalizeTarPath(path) {
const normalized = path.replaceAll("\\", "/").replace(/^\.\/+/, "");
if (!normalized || normalized.startsWith("/") || normalized.includes("\0"))
return null;
const segments = normalized.split("/").filter(Boolean);
if (segments.length === 0 || segments.some((segment) => segment === "." || segment === "..")) {
return null;
}
return segments.join("/");
}
function isZeroBlock(block) {
return block.every((byte) => byte === 0);
}
function nextTarOffset(offset, size) {
return offset + Math.ceil(size / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;
}
function parseTarEntries(bytes) {
const entries = [];
let offset = 0;
while (offset + TAR_BLOCK_SIZE <= bytes.byteLength) {
const header = bytes.subarray(offset, offset + TAR_BLOCK_SIZE);
if (isZeroBlock(header))
break;
const name = readTarString(header, 0, 100);
const prefix = readTarString(header, 345, 155);
const path = normalizeTarPath(prefix ? `${prefix}/${name}` : name);
if (!path)
throw new Error("ClawPack contains an unsafe tar path");
const size = readTarSize(header);
const payloadOffset = offset + TAR_BLOCK_SIZE;
const payloadEnd = payloadOffset + size;
if (payloadEnd > bytes.byteLength)
throw new Error("ClawPack tar entry is truncated");
const typeflag = String.fromCharCode(header[156] ?? 0).replace("\0", "");
if (typeflag === "" || typeflag === "0") {
if (!path.startsWith("package/")) {
throw new Error("ClawPack entries must be rooted under package/");
}
const relPath = path.slice("package/".length);
if (relPath) {
entries.push({
path: relPath,
bytes: Uint8Array.from(bytes.subarray(payloadOffset, payloadEnd)),
});
}
}
else if (typeflag !== "5") {
throw new Error("ClawPack may only contain regular files and directories");
}
offset = nextTarOffset(payloadOffset, size);
}
if (entries.length === 0)
throw new Error("ClawPack contains no files");
return entries;
}
export function parseClawPack(bytes) {
let tarBytes;
try {
tarBytes = gunzipSync(bytes);
}
catch {
throw new Error("ClawPack must be a gzip-compressed npm pack tarball");
}
const entries = parseTarEntries(tarBytes);
const packageJsonEntry = entries.find((entry) => entry.path === "package.json");
if (!packageJsonEntry)
throw new Error("ClawPack must contain package/package.json");
const pluginManifestEntry = entries.find((entry) => entry.path === "openclaw.plugin.json");
if (!pluginManifestEntry) {
throw new Error("ClawPack must contain package/openclaw.plugin.json");
}
let packageJson;
try {
packageJson = JSON.parse(textFromBytes(packageJsonEntry.bytes));
}
catch {
throw new Error("ClawPack package.json is invalid JSON");
}
if (!isRecord(packageJson))
throw new Error("ClawPack package.json must be an object");
const packageName = typeof packageJson.name === "string" ? packageJson.name.trim() : "";
const packageVersion = typeof packageJson.version === "string" ? packageJson.version.trim() : "";
if (!packageName)
throw new Error("ClawPack package.json must declare a name");
if (!packageVersion)
throw new Error("ClawPack package.json must declare a version");
let pluginManifest;
try {
pluginManifest = JSON.parse(textFromBytes(pluginManifestEntry.bytes));
}
catch {
throw new Error("ClawPack openclaw.plugin.json is invalid JSON");
}
if (!isRecord(pluginManifest)) {
throw new Error("ClawPack openclaw.plugin.json must be an object");
}
return {
packageName,
packageVersion,
entries,
packageJson,
pluginManifest,
};
}
//# sourceMappingURL=clawpack.js.map