@eigenpal/cli 0.17.5 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +201 -17
- package/dist/docs-bundle.json +26 -5
- package/dist/skill/reference/step-types.md +265 -0
- package/dist/skill/reference/workflow-yaml.md +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7803,6 +7803,7 @@ var init_ids = __esm(() => {
|
|
|
7803
7803
|
FIGURE_CROPS: "builtin/figure-crops",
|
|
7804
7804
|
TEMPLATE: "builtin/template",
|
|
7805
7805
|
PDF_EMBEDDER: "builtin/pdf-embedder",
|
|
7806
|
+
CROP_REGIONS: "builtin/crop-regions",
|
|
7806
7807
|
XLSX_TO_JSON: "builtin/xlsx-to-json",
|
|
7807
7808
|
JSON_TO_XLSX: "builtin/json-to-xlsx",
|
|
7808
7809
|
ARCHIVE_LIST: "builtin/archive-list",
|
|
@@ -8164,7 +8165,8 @@ var init_parser = __esm(() => {
|
|
|
8164
8165
|
ParseUsageSchema = object({
|
|
8165
8166
|
pageCount: number2(),
|
|
8166
8167
|
processingTimeMs: number2().optional(),
|
|
8167
|
-
ocrPagesProcessed: number2().int().nonnegative().optional()
|
|
8168
|
+
ocrPagesProcessed: number2().int().nonnegative().optional(),
|
|
8169
|
+
cached: boolean2().optional()
|
|
8168
8170
|
});
|
|
8169
8171
|
StructuredDocumentSchema = record(string2(), unknown());
|
|
8170
8172
|
CoordinateUnitSchema = _enum(["pixel", "inch", "point", "normalized"]);
|
|
@@ -8350,7 +8352,8 @@ var init_document_parser = __esm(() => {
|
|
|
8350
8352
|
describeFigures: boolean2().optional().describe("Opt-in (default off). After text extraction, detect figure pages with a layout model and caption them with a vision model, appending `<figure>description</figure>` to their text. Skipped for plaintext. The caption vision calls are billed."),
|
|
8351
8353
|
figureInstructions: string2().optional().describe("Custom instruction for the figure-description pass."),
|
|
8352
8354
|
languages: array(string2()).optional().describe('OCR language hints (e.g., ["en", "de"])'),
|
|
8353
|
-
outputFormat: ParseOutputFormatSchema.default("markdown").optional().describe(PARSE_OUTPUT_FORMAT_DESCRIPTION)
|
|
8355
|
+
outputFormat: ParseOutputFormatSchema.default("markdown").optional().describe(PARSE_OUTPUT_FORMAT_DESCRIPTION),
|
|
8356
|
+
cache: boolean2().default(false).optional().describe("When true, reuse a prior parse for identical file bytes and parse settings. Skips OCR/vision on cache hit. Stored in tenant blob storage with no automatic expiry (TTL/lifecycle is a follow-up). Default off.")
|
|
8354
8357
|
}).superRefine(refineNativeParseModeConflicts).superRefine(refineLayoutOutputFormat).prefault({});
|
|
8355
8358
|
});
|
|
8356
8359
|
|
|
@@ -53691,6 +53694,7 @@ var init_steps = __esm(() => {
|
|
|
53691
53694
|
"transform.merge",
|
|
53692
53695
|
"transform.template",
|
|
53693
53696
|
"transform.pdf-embed",
|
|
53697
|
+
"transform.crop-regions",
|
|
53694
53698
|
"transform.xlsx-to-json",
|
|
53695
53699
|
"transform.json-to-xlsx",
|
|
53696
53700
|
"transform.script",
|
|
@@ -53839,7 +53843,7 @@ function getStepSchema(stepType) {
|
|
|
53839
53843
|
function listStepTypes() {
|
|
53840
53844
|
return [...STEP_TYPES];
|
|
53841
53845
|
}
|
|
53842
|
-
var OptionalReasoningEffortSchema, AiParseConfigSchema, AiParseOutputSchema, AiFigureCropsConfigSchema, AiFigureCropsFigureSchema, AiFigureCropsOutputSchema, AiExtractConfigSchema, AiExtractOutputSchema, VisionPageRangeSchema, AiVisionConfigSchema, AiVisionOutputSchema, AI_SEARCH_FILES_DEFAULT_MAX_ITERATIONS = 12, AI_SEARCH_FILES_MAX_ITERATIONS = 50, AI_SEARCH_FILES_DEFAULT_MAX_FILES = 50, AI_SEARCH_FILES_MAX_FILES = 200, AI_SEARCH_FILES_MAX_CITATION_EXCERPT_CHARS = 2000, AiSearchFilesConfigSchema, AiSearchFilesCitationSchema, AiSearchFilesOutputSchema, AiSplitConfigSchema, AiSplitSectionSchema, AiSplitOutputSchema, AiSegmentConfigSchema, AiSegmentOutputSchema, AiClassifyConfigSchema, AiClassifyOutputSchema, ClassifyPagesLabelSchema2, AiClassifyPagesConfigSchema, AiClassifyPagesOutputSchema, TransformSetConfigSchema, TransformSetOutputSchema, TransformRemoveConfigSchema, TransformRemoveOutputSchema, TransformCombineConfigSchema, TransformCombineOutputSchema, TransformSplitConfigSchema, TransformSplitOutputSchema, TransformMergeConfigSchema, TransformMergeOutputSchema, TransformTemplateConfigSchema, TransformTemplateOutputSchema, TransformPdfEmbedConfigSchema, TransformPdfEmbedOutputSchema, TransformXlsxToJsonConfigSchema, TransformXlsxToJsonOutputSchema, JsonToXlsxAuthoringRowsSchema, JsonToXlsxSheetConfigSchema, TransformJsonToXlsxConfigSchema, TransformJsonToXlsxOutputSchema, ARCHIVE_LIST_MAX_ENTRIES = 1e4, TransformArchiveListConfigSchema, TransformArchiveListEntrySchema, TransformArchiveListOutputSchema, TransformArchiveExtractConfigSchema, TransformArchiveExtractOutputSchema, TransformScriptConfigSchema, TransformScriptOutputSchema, ParsedDocPageSchema, TransformTextChunkerConfigSchema, TransformTextChunkerOutputSchema, RegexNormalizerSchema, RegexFormatSchema, REGEX_FLAGS, RegexFieldSchema, TransformRegexExtractConfigSchema, TransformRegexExtractOutputSchema, ActionHttpConfigSchema, ActionHttpOutputSchema, ActionInvokeWorkflowConfigSchema, ActionInvokeWorkflowOutputSchema, ActionWebsiteReaderConfigSchema, ActionWebsiteReaderOutputSchema, ACTION_EMAIL_MAX_ATTACHMENTS = 10, ACTION_EMAIL_MAX_ATTACHMENT_BYTES, ACTION_EMAIL_BODY_MAX_BYTES, emailBodyMaxBytes = (value) => new TextEncoder().encode(value).byteLength <= ACTION_EMAIL_BODY_MAX_BYTES, ActionEmailRecipientListSchema, ActionEmailToSchema, ActionEmailOptionalRecipientsSchema, ActionEmailAttachmentSchema, ActionEmailConfigSchema, ActionEmailOutputSchema, ControlIfConfigSchema, ControlIfOutputSchema, ControlSwitchConfigSchema, ControlSwitchOutputSchema, ControlForeachConfigSchema, ControlForeachOutputSchema, ControlParallelMapConfigSchema, ControlParallelMapOutputSchema, ControlParallelConfigSchema, ControlParallelOutputSchema, ControlWaitConfigSchema, ControlWaitOutputSchema, ControlHumanReviewConfigSchema, ControlHumanReviewOutputSchema, ControlFailConfigSchema, ControlFailOutputSchema, STEP_SCHEMAS;
|
|
53846
|
+
var OptionalReasoningEffortSchema, AiParseConfigSchema, AiParseOutputSchema, AiFigureCropsConfigSchema, AiFigureCropsFigureSchema, AiFigureCropsOutputSchema, AiExtractConfigSchema, AiExtractOutputSchema, VisionPageRangeSchema, AiVisionConfigSchema, AiVisionOutputSchema, AI_SEARCH_FILES_DEFAULT_MAX_ITERATIONS = 12, AI_SEARCH_FILES_MAX_ITERATIONS = 50, AI_SEARCH_FILES_DEFAULT_MAX_FILES = 50, AI_SEARCH_FILES_MAX_FILES = 200, AI_SEARCH_FILES_MAX_CITATION_EXCERPT_CHARS = 2000, AiSearchFilesConfigSchema, AiSearchFilesCitationSchema, AiSearchFilesOutputSchema, AiSplitConfigSchema, AiSplitSectionSchema, AiSplitOutputSchema, AiSegmentConfigSchema, AiSegmentOutputSchema, AiClassifyConfigSchema, AiClassifyOutputSchema, ClassifyPagesLabelSchema2, AiClassifyPagesConfigSchema, AiClassifyPagesOutputSchema, TransformSetConfigSchema, TransformSetOutputSchema, TransformRemoveConfigSchema, TransformRemoveOutputSchema, TransformCombineConfigSchema, TransformCombineOutputSchema, TransformSplitConfigSchema, TransformSplitOutputSchema, TransformMergeConfigSchema, TransformMergeOutputSchema, TransformTemplateConfigSchema, TransformTemplateOutputSchema, TransformPdfEmbedConfigSchema, TransformPdfEmbedOutputSchema, CropRegionBboxSchema, CropRegionSpecSchema, CropRegionSpecListSchema, CropRegionSpecListResolvedSchema, TransformCropRegionsConfigSchema, TransformCropRegionsCropSchema, TransformCropRegionsOutputSchema, CropRegionsProcessorConfigSchema, TransformXlsxToJsonConfigSchema, TransformXlsxToJsonOutputSchema, JsonToXlsxAuthoringRowsSchema, JsonToXlsxSheetConfigSchema, TransformJsonToXlsxConfigSchema, TransformJsonToXlsxOutputSchema, ARCHIVE_LIST_MAX_ENTRIES = 1e4, TransformArchiveListConfigSchema, TransformArchiveListEntrySchema, TransformArchiveListOutputSchema, TransformArchiveExtractConfigSchema, TransformArchiveExtractOutputSchema, TransformScriptConfigSchema, TransformScriptOutputSchema, ParsedDocPageSchema, TransformTextChunkerConfigSchema, TransformTextChunkerOutputSchema, RegexNormalizerSchema, RegexFormatSchema, REGEX_FLAGS, RegexFieldSchema, TransformRegexExtractConfigSchema, TransformRegexExtractOutputSchema, ActionHttpConfigSchema, ActionHttpOutputSchema, ActionInvokeWorkflowConfigSchema, ActionInvokeWorkflowOutputSchema, ActionWebsiteReaderConfigSchema, ActionWebsiteReaderOutputSchema, ACTION_EMAIL_MAX_ATTACHMENTS = 10, ACTION_EMAIL_MAX_ATTACHMENT_BYTES, ACTION_EMAIL_BODY_MAX_BYTES, emailBodyMaxBytes = (value) => new TextEncoder().encode(value).byteLength <= ACTION_EMAIL_BODY_MAX_BYTES, ActionEmailRecipientListSchema, ActionEmailToSchema, ActionEmailOptionalRecipientsSchema, ActionEmailAttachmentSchema, ActionEmailConfigSchema, ActionEmailOutputSchema, ControlIfConfigSchema, ControlIfOutputSchema, ControlSwitchConfigSchema, ControlSwitchOutputSchema, ControlForeachConfigSchema, ControlForeachOutputSchema, ControlParallelMapConfigSchema, ControlParallelMapOutputSchema, ControlParallelConfigSchema, ControlParallelOutputSchema, ControlWaitConfigSchema, ControlWaitOutputSchema, ControlHumanReviewConfigSchema, ControlHumanReviewOutputSchema, ControlFailConfigSchema, ControlFailOutputSchema, STEP_SCHEMAS;
|
|
53843
53847
|
var init_step_configs = __esm(() => {
|
|
53844
53848
|
init_zod();
|
|
53845
53849
|
init_ai_client();
|
|
@@ -53870,7 +53874,8 @@ var init_step_configs = __esm(() => {
|
|
|
53870
53874
|
outputFormat: ParseOutputFormatSchema.default("markdown").optional().describe(PARSE_OUTPUT_FORMAT_DESCRIPTION),
|
|
53871
53875
|
nativeText: boolean2().default(false).optional().describe("Extract native/embedded text from PDFs without OCR/VLM. Faster and uses no credits. Falls back to OCR/VLM if the PDF has no embedded text."),
|
|
53872
53876
|
describeFigures: boolean2().optional().describe("Opt-in (default off). After text extraction, detect which pages contain figures with an in-worker layout model, then caption those pages with a vision model and append `<figure>description</figure>` to their text — so image-only pages (property photos, signatures, charts) become findable by text-based steps like ai.split. Note: the layout scan runs over all pages, and the caption step and its vision calls are billed. Skipped for plaintext."),
|
|
53873
|
-
figureInstructions: string2().optional().describe('Custom instruction for the figure-description pass, e.g. "Describe each figure; label a handwritten signature as `<figure>signature</figure>` and a stamp as `<figure>stamp</figure>`; for property photos note the room or exterior shown." Applied only when describeFigures runs.')
|
|
53877
|
+
figureInstructions: string2().optional().describe('Custom instruction for the figure-description pass, e.g. "Describe each figure; label a handwritten signature as `<figure>signature</figure>` and a stamp as `<figure>stamp</figure>`; for property photos note the room or exterior shown." Applied only when describeFigures runs.'),
|
|
53878
|
+
cache: boolean2().default(false).optional().describe("When true, reuse a prior parse for identical file bytes and parse settings. Skips OCR/vision on cache hit. Stored in tenant blob storage with no automatic expiry (TTL/lifecycle is a follow-up). Default off.")
|
|
53874
53879
|
}).superRefine(refineNativeParseModeConflicts).superRefine(refineLayoutOutputFormat);
|
|
53875
53880
|
AiParseOutputSchema = ParseResultSchema.omit({ rawResponse: true });
|
|
53876
53881
|
AiFigureCropsConfigSchema = object({
|
|
@@ -54220,6 +54225,55 @@ var init_step_configs = __esm(() => {
|
|
|
54220
54225
|
wordCount: number2().describe("Number of words embedded"),
|
|
54221
54226
|
text: string2().describe("Extracted text from the document")
|
|
54222
54227
|
});
|
|
54228
|
+
CropRegionBboxSchema = tuple([
|
|
54229
|
+
number2().min(0).max(1),
|
|
54230
|
+
number2().min(0).max(1),
|
|
54231
|
+
number2().min(0).max(1),
|
|
54232
|
+
number2().min(0).max(1)
|
|
54233
|
+
]);
|
|
54234
|
+
CropRegionSpecSchema = object({
|
|
54235
|
+
id: string2().optional().describe("Stable region id used in output and crop filenames (defaults to region-N)"),
|
|
54236
|
+
pageIndex: number2().int().min(0).default(0).optional().describe("0-based page index to crop from (images use page 0)"),
|
|
54237
|
+
bbox: CropRegionBboxSchema.describe("Normalized axis-aligned bbox [x0, y0, x1, y1]")
|
|
54238
|
+
});
|
|
54239
|
+
CropRegionSpecListSchema = array(CropRegionSpecSchema).min(1).describe("Regions to crop from the rendered document");
|
|
54240
|
+
CropRegionSpecListResolvedSchema = array(CropRegionSpecSchema).describe("Regions to crop from the rendered document (may be empty)");
|
|
54241
|
+
TransformCropRegionsConfigSchema = object({
|
|
54242
|
+
input: string2().describe("File input — template expression e.g. {{ input.document }} resolving to a PDF or image"),
|
|
54243
|
+
regions: union([
|
|
54244
|
+
string2().min(1).describe("Template expression resolving to a region array, e.g. {{ steps.list-figures.output.regions }}"),
|
|
54245
|
+
CropRegionSpecListSchema
|
|
54246
|
+
]).describe("Regions to crop — pageIndex + normalized bbox per entry"),
|
|
54247
|
+
renderScale: number2().min(0.5).max(4).default(1).optional().describe("Scale factor when rasterizing PDF pages before cropping"),
|
|
54248
|
+
imageQuality: number2().int().min(1).max(100).default(85).optional().describe("JPEG quality for cropped outputs"),
|
|
54249
|
+
paddingFrac: number2().min(0).max(0.2).default(0.02).optional().describe("Padding around each bbox as a fraction of the shorter page edge"),
|
|
54250
|
+
minCropPx: number2().int().min(1).default(8).optional().describe("Minimum crop width/height in pixels; smaller crops fall back to the full page"),
|
|
54251
|
+
maxRegions: number2().int().min(1).max(200).default(100).optional().describe("Maximum regions processed per invocation")
|
|
54252
|
+
});
|
|
54253
|
+
TransformCropRegionsCropSchema = object({
|
|
54254
|
+
id: string2().describe("Region id (from input or auto-generated)"),
|
|
54255
|
+
pageIndex: number2().int().min(0).describe("0-based page index the crop was taken from"),
|
|
54256
|
+
bbox: CropRegionBboxSchema.describe("Normalized bbox that was cropped"),
|
|
54257
|
+
width: number2().int().min(1).describe("Crop width in pixels"),
|
|
54258
|
+
height: number2().int().min(1).describe("Crop height in pixels"),
|
|
54259
|
+
fileId: string2().describe("Run output file id for the JPEG crop"),
|
|
54260
|
+
filename: string2().describe("Stored crop filename"),
|
|
54261
|
+
mimeType: literal("image/jpeg").describe("Always image/jpeg"),
|
|
54262
|
+
size: number2().int().min(0).describe("JPEG size in bytes")
|
|
54263
|
+
});
|
|
54264
|
+
TransformCropRegionsOutputSchema = object({
|
|
54265
|
+
regions: array(TransformCropRegionsCropSchema).describe("Successfully cropped regions"),
|
|
54266
|
+
skipped: number2().int().min(0).optional().describe("Regions skipped (invalid bbox or missing page)")
|
|
54267
|
+
});
|
|
54268
|
+
CropRegionsProcessorConfigSchema = TransformCropRegionsConfigSchema.omit({
|
|
54269
|
+
input: true,
|
|
54270
|
+
regions: true
|
|
54271
|
+
}).extend({
|
|
54272
|
+
regions: union([
|
|
54273
|
+
string2().min(1).describe("Template expression resolving to a region array, e.g. {{ steps.list-figures.output.regions }}"),
|
|
54274
|
+
CropRegionSpecListResolvedSchema
|
|
54275
|
+
]).describe("Regions to crop — may be empty when upstream scripts find no figures")
|
|
54276
|
+
});
|
|
54223
54277
|
TransformXlsxToJsonConfigSchema = object({
|
|
54224
54278
|
input: string2().min(1, "Input is required").describe("File input - template expression e.g. {{input.document}} resolving to a scoped $file artifact at runtime")
|
|
54225
54279
|
}).extend(XlsxToJsonConfigSchema.shape).extend({
|
|
@@ -54698,6 +54752,15 @@ var init_step_configs = __esm(() => {
|
|
|
54698
54752
|
outputSchema: TransformPdfEmbedOutputSchema,
|
|
54699
54753
|
configInWith: true
|
|
54700
54754
|
},
|
|
54755
|
+
"transform.crop-regions": {
|
|
54756
|
+
type: "transform.crop-regions",
|
|
54757
|
+
category: "transform",
|
|
54758
|
+
name: "Crop Regions",
|
|
54759
|
+
description: "Render a PDF or image and crop normalized bounding boxes into JPEG run output artifacts. Pair with transform.script (bbox lists from ai.parse layout) and ai.extract or ai.vision for captions.",
|
|
54760
|
+
configSchema: TransformCropRegionsConfigSchema,
|
|
54761
|
+
outputSchema: TransformCropRegionsOutputSchema,
|
|
54762
|
+
configInWith: true
|
|
54763
|
+
},
|
|
54701
54764
|
"transform.xlsx-to-json": {
|
|
54702
54765
|
type: "transform.xlsx-to-json",
|
|
54703
54766
|
category: "transform",
|
|
@@ -206317,7 +206380,7 @@ import { fileURLToPath as fileURLToPath8 } from "url";
|
|
|
206317
206380
|
// package.json
|
|
206318
206381
|
var package_default = {
|
|
206319
206382
|
name: "@eigenpal/cli",
|
|
206320
|
-
version: "0.
|
|
206383
|
+
version: "0.19.0",
|
|
206321
206384
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
206322
206385
|
keywords: [
|
|
206323
206386
|
"eigenpal",
|
|
@@ -211955,12 +212018,22 @@ function inspectXlsxTemplatePlaceholders(buffer) {
|
|
|
211955
212018
|
var import_pizzip2 = __toESM(require_js(), 1);
|
|
211956
212019
|
|
|
211957
212020
|
// ../common/src/office-zip-safety.ts
|
|
212021
|
+
import { inflateRawSync } from "node:zlib";
|
|
211958
212022
|
var MAX_TEMPLATE_INFLATED_BYTES = 100 * 1024 * 1024;
|
|
211959
212023
|
var MAX_TEMPLATE_ZIP_ENTRIES = 1e4;
|
|
211960
|
-
var MAX_TEMPLATE_COMPRESSION_RATIO =
|
|
212024
|
+
var MAX_TEMPLATE_COMPRESSION_RATIO = 1100;
|
|
212025
|
+
var MAX_TEMPLATE_DEFLATE_EXPANSION_SLACK_BYTES = 1024;
|
|
212026
|
+
function maxPlausibleDeflatedSize(inflated) {
|
|
212027
|
+
return inflated * 2 + MAX_TEMPLATE_DEFLATE_EXPANSION_SLACK_BYTES;
|
|
212028
|
+
}
|
|
212029
|
+
var VERIFY_CHUNK_CAP_BYTES = 256 * 1024;
|
|
212030
|
+
var VERIFY_CHUNK_FLOOR_BYTES = 16 * 1024;
|
|
211961
212031
|
var EOCD_SIGNATURE = 101010256;
|
|
211962
212032
|
var CENTRAL_SIGNATURE = 33639248;
|
|
211963
212033
|
var LOCAL_FILE_SIGNATURE = 67324752;
|
|
212034
|
+
var METHOD_STORE = 0;
|
|
212035
|
+
var METHOD_DEFLATE = 8;
|
|
212036
|
+
var FLAG_ENCRYPTED = 1;
|
|
211964
212037
|
function isOfficeZipContainer(bytes) {
|
|
211965
212038
|
return bytes.length >= 4 && bytes.readUInt32LE(0) === LOCAL_FILE_SIGNATURE;
|
|
211966
212039
|
}
|
|
@@ -211973,7 +212046,93 @@ function assertSafeZipPath(path) {
|
|
|
211973
212046
|
throw new Error("Office ZIP entry path is not safe");
|
|
211974
212047
|
}
|
|
211975
212048
|
}
|
|
211976
|
-
function
|
|
212049
|
+
function locateEntryPayload(bytes, compressed, localOffset) {
|
|
212050
|
+
if (localOffset + 30 > bytes.length || bytes.readUInt32LE(localOffset) !== LOCAL_FILE_SIGNATURE) {
|
|
212051
|
+
throw new Error("Invalid Office ZIP local file header");
|
|
212052
|
+
}
|
|
212053
|
+
const localNameLength = bytes.readUInt16LE(localOffset + 26);
|
|
212054
|
+
const localExtraLength = bytes.readUInt16LE(localOffset + 28);
|
|
212055
|
+
const dataStart = localOffset + 30 + localNameLength + localExtraLength;
|
|
212056
|
+
const dataEnd = dataStart + compressed;
|
|
212057
|
+
if (dataEnd > bytes.length) {
|
|
212058
|
+
throw new Error("Invalid Office ZIP local file header");
|
|
212059
|
+
}
|
|
212060
|
+
return bytes.subarray(dataStart, dataEnd);
|
|
212061
|
+
}
|
|
212062
|
+
function assertDeclaredSizeIsReal(bytes, entry, localOffset) {
|
|
212063
|
+
const payload = locateEntryPayload(bytes, entry.compressed, localOffset);
|
|
212064
|
+
if (entry.method === METHOD_STORE) {
|
|
212065
|
+
if (entry.compressed !== entry.inflated) {
|
|
212066
|
+
throw new Error("Office ZIP entry size does not match its stored payload");
|
|
212067
|
+
}
|
|
212068
|
+
return;
|
|
212069
|
+
}
|
|
212070
|
+
if (entry.compressed === 0 && entry.inflated === 0)
|
|
212071
|
+
return;
|
|
212072
|
+
try {
|
|
212073
|
+
const inflated = inflateRawSync(payload, {
|
|
212074
|
+
maxOutputLength: Math.max(entry.inflated, 1),
|
|
212075
|
+
chunkSize: verifyChunkSize(entry)
|
|
212076
|
+
});
|
|
212077
|
+
if (inflated.length !== entry.inflated) {
|
|
212078
|
+
throw new Error("Office ZIP entry size does not match its compressed payload");
|
|
212079
|
+
}
|
|
212080
|
+
} catch (error) {
|
|
212081
|
+
if (error instanceof Error && error.message.includes("does not match"))
|
|
212082
|
+
throw error;
|
|
212083
|
+
throw new Error("Office ZIP entry inflates beyond its declared uncompressed size");
|
|
212084
|
+
}
|
|
212085
|
+
}
|
|
212086
|
+
function verifyChunkSize(entry) {
|
|
212087
|
+
const wanted = Math.min(entry.inflated + 1, VERIFY_CHUNK_CAP_BYTES);
|
|
212088
|
+
return Math.max(wanted, VERIFY_CHUNK_FLOOR_BYTES);
|
|
212089
|
+
}
|
|
212090
|
+
var ZIP64_EXTRA_HEADER_ID = 1;
|
|
212091
|
+
var ZIP32_SENTINEL = 4294967295;
|
|
212092
|
+
var ZIP16_SENTINEL = 65535;
|
|
212093
|
+
function resolveEntrySizes(bytes, recordOffset, extraStart, extraLength) {
|
|
212094
|
+
let compressed = bytes.readUInt32LE(recordOffset + 20);
|
|
212095
|
+
let inflated = bytes.readUInt32LE(recordOffset + 24);
|
|
212096
|
+
let localOffset = bytes.readUInt32LE(recordOffset + 42);
|
|
212097
|
+
const diskStart = bytes.readUInt16LE(recordOffset + 34);
|
|
212098
|
+
const needsZip64 = compressed === ZIP32_SENTINEL || inflated === ZIP32_SENTINEL || localOffset === ZIP32_SENTINEL || diskStart === ZIP16_SENTINEL;
|
|
212099
|
+
if (!needsZip64)
|
|
212100
|
+
return { compressed, inflated, localOffset };
|
|
212101
|
+
let cursor = extraStart;
|
|
212102
|
+
const extraEnd = extraStart + extraLength;
|
|
212103
|
+
while (cursor + 4 <= extraEnd) {
|
|
212104
|
+
const headerId = bytes.readUInt16LE(cursor);
|
|
212105
|
+
const size = bytes.readUInt16LE(cursor + 2);
|
|
212106
|
+
const body = cursor + 4;
|
|
212107
|
+
if (body + size > extraEnd)
|
|
212108
|
+
break;
|
|
212109
|
+
if (headerId !== ZIP64_EXTRA_HEADER_ID) {
|
|
212110
|
+
cursor = body + size;
|
|
212111
|
+
continue;
|
|
212112
|
+
}
|
|
212113
|
+
let field = body;
|
|
212114
|
+
const takeUInt64 = () => {
|
|
212115
|
+
if (field + 8 > body + size) {
|
|
212116
|
+
throw new Error("Invalid Office ZIP central directory: truncated ZIP64 extra field");
|
|
212117
|
+
}
|
|
212118
|
+
const value = bytes.readBigUInt64LE(field);
|
|
212119
|
+
field += 8;
|
|
212120
|
+
if (value > BigInt(bytes.length)) {
|
|
212121
|
+
throw new Error("Office ZIP exceeds safe archive bounds");
|
|
212122
|
+
}
|
|
212123
|
+
return Number(value);
|
|
212124
|
+
};
|
|
212125
|
+
if (inflated === ZIP32_SENTINEL)
|
|
212126
|
+
inflated = takeUInt64();
|
|
212127
|
+
if (compressed === ZIP32_SENTINEL)
|
|
212128
|
+
compressed = takeUInt64();
|
|
212129
|
+
if (localOffset === ZIP32_SENTINEL)
|
|
212130
|
+
localOffset = takeUInt64();
|
|
212131
|
+
return { compressed, inflated, localOffset };
|
|
212132
|
+
}
|
|
212133
|
+
throw new Error("Invalid Office ZIP central directory: missing ZIP64 extra field");
|
|
212134
|
+
}
|
|
212135
|
+
function readCentralDirectory(bytes) {
|
|
211977
212136
|
let eocd = -1;
|
|
211978
212137
|
for (let offset = bytes.length - 22;offset >= Math.max(0, bytes.length - 65557); offset -= 1) {
|
|
211979
212138
|
if (bytes.readUInt32LE(offset) === EOCD_SIGNATURE) {
|
|
@@ -211989,25 +212148,37 @@ function inspectSafeOfficeZip(bytes) {
|
|
|
211989
212148
|
if (entries > MAX_TEMPLATE_ZIP_ENTRIES || centralOffset + centralSize > bytes.length) {
|
|
211990
212149
|
throw new Error("Office ZIP exceeds safe archive bounds");
|
|
211991
212150
|
}
|
|
211992
|
-
const
|
|
212151
|
+
const parsed = [];
|
|
211993
212152
|
let offset = centralOffset;
|
|
211994
212153
|
let inflatedTotal = 0;
|
|
211995
212154
|
for (let index = 0;index < entries; index += 1) {
|
|
211996
212155
|
if (offset + 46 > bytes.length || bytes.readUInt32LE(offset) !== CENTRAL_SIGNATURE) {
|
|
211997
212156
|
throw new Error("Invalid Office ZIP central directory");
|
|
211998
212157
|
}
|
|
211999
|
-
const
|
|
212000
|
-
const
|
|
212001
|
-
|
|
212002
|
-
|
|
212158
|
+
const flags = bytes.readUInt16LE(offset + 8);
|
|
212159
|
+
const method = bytes.readUInt16LE(offset + 10);
|
|
212160
|
+
const filenameLength = bytes.readUInt16LE(offset + 28);
|
|
212161
|
+
const extraLength = bytes.readUInt16LE(offset + 30);
|
|
212162
|
+
const commentLength = bytes.readUInt16LE(offset + 32);
|
|
212163
|
+
const extraStart = offset + 46 + filenameLength;
|
|
212164
|
+
if (extraStart + extraLength > bytes.length) {
|
|
212165
|
+
throw new Error("Invalid Office ZIP central directory");
|
|
212166
|
+
}
|
|
212167
|
+
const sizes = resolveEntrySizes(bytes, offset, extraStart, extraLength);
|
|
212168
|
+
const { compressed, inflated, localOffset } = sizes;
|
|
212169
|
+
if ((flags & FLAG_ENCRYPTED) !== 0) {
|
|
212170
|
+
throw new Error("Encrypted Office templates are not supported");
|
|
212171
|
+
}
|
|
212172
|
+
if (method !== METHOD_STORE && method !== METHOD_DEFLATE) {
|
|
212173
|
+
throw new Error(`Unsupported Office ZIP compression method ${method}`);
|
|
212003
212174
|
}
|
|
212004
212175
|
inflatedTotal += inflated;
|
|
212005
212176
|
if (inflatedTotal > MAX_TEMPLATE_INFLATED_BYTES || inflated > 1024 * 1024 && compressed > 0 && inflated / compressed > MAX_TEMPLATE_COMPRESSION_RATIO) {
|
|
212006
212177
|
throw new Error("Office ZIP exceeds safe inflated-size bounds");
|
|
212007
212178
|
}
|
|
212008
|
-
|
|
212009
|
-
|
|
212010
|
-
|
|
212179
|
+
if (compressed > maxPlausibleDeflatedSize(inflated)) {
|
|
212180
|
+
throw new Error("Office ZIP declares an implausibly small uncompressed size");
|
|
212181
|
+
}
|
|
212011
212182
|
const nameStart = offset + 46;
|
|
212012
212183
|
const nameEnd = nameStart + filenameLength;
|
|
212013
212184
|
if (nameEnd > bytes.length) {
|
|
@@ -212015,10 +212186,23 @@ function inspectSafeOfficeZip(bytes) {
|
|
|
212015
212186
|
}
|
|
212016
212187
|
const path = new TextDecoder().decode(bytes.subarray(nameStart, nameEnd));
|
|
212017
212188
|
assertSafeZipPath(path);
|
|
212018
|
-
|
|
212189
|
+
parsed.push({ entry: { path, compressed, inflated, method }, localOffset });
|
|
212019
212190
|
offset = nameEnd + extraLength + commentLength;
|
|
212020
212191
|
}
|
|
212021
|
-
|
|
212192
|
+
if (offset !== centralOffset + centralSize) {
|
|
212193
|
+
throw new Error("Invalid Office ZIP central directory: declared size does not match entries");
|
|
212194
|
+
}
|
|
212195
|
+
if (offset + 4 <= bytes.length && bytes.readUInt32LE(offset) === CENTRAL_SIGNATURE) {
|
|
212196
|
+
throw new Error("Invalid Office ZIP central directory: more entries than declared");
|
|
212197
|
+
}
|
|
212198
|
+
return parsed;
|
|
212199
|
+
}
|
|
212200
|
+
function inspectSafeOfficeZip(bytes) {
|
|
212201
|
+
const parsed = readCentralDirectory(bytes);
|
|
212202
|
+
for (const { entry, localOffset } of parsed) {
|
|
212203
|
+
assertDeclaredSizeIsReal(bytes, entry, localOffset);
|
|
212204
|
+
}
|
|
212205
|
+
return { entries: parsed.map(({ entry }) => entry) };
|
|
212022
212206
|
}
|
|
212023
212207
|
function assertSafeOfficeZip(bytes) {
|
|
212024
212208
|
return inspectSafeOfficeZip(bytes);
|