web-doc 0.6.2 → 0.8.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/THIRD_PARTY_NOTICES.md +33 -1
- package/dist/adapters/docx-images.d.ts +13 -4
- package/dist/adapters/docx-images.js +3 -0
- package/dist/adapters/docx-paragraphs.d.ts +65 -0
- package/dist/adapters/docx-paragraphs.js +165 -0
- package/dist/adapters/docx-prepass.d.ts +17 -0
- package/dist/adapters/docx-prepass.js +230 -0
- package/dist/adapters/office.d.ts +31 -5
- package/dist/adapters/office.js +60 -29
- package/dist/adapters/pdf.d.ts +9 -0
- package/dist/adapters/pdf.js +10 -0
- package/dist/assets/pdfium/pdfium.wasm +0 -0
- package/dist/contracts.d.ts +53 -2
- package/dist/edit/ai/outline.d.ts +47 -0
- package/dist/edit/ai/outline.js +338 -0
- package/dist/edit/ai/targets.d.ts +16 -0
- package/dist/edit/ai/targets.js +309 -0
- package/dist/edit/ai/tools.d.ts +28 -0
- package/dist/edit/ai/tools.js +605 -0
- package/dist/edit/ai/types.d.ts +175 -0
- package/dist/edit/ai/types.js +1 -0
- package/dist/edit/assets.d.ts +23 -0
- package/dist/edit/assets.js +75 -0
- package/dist/edit/docx/elements.d.ts +6 -0
- package/dist/edit/docx/elements.js +90 -0
- package/dist/edit/docx/engine.d.ts +57 -0
- package/dist/edit/docx/engine.js +547 -0
- package/dist/edit/docx/handlers.d.ts +3 -0
- package/dist/edit/docx/handlers.js +15 -0
- package/dist/edit/docx/ids.d.ts +45 -0
- package/dist/edit/docx/ids.js +101 -0
- package/dist/edit/docx/model.d.ts +94 -0
- package/dist/edit/docx/model.js +350 -0
- package/dist/edit/docx/operations.d.ts +47 -0
- package/dist/edit/docx/operations.js +3 -0
- package/dist/edit/docx/provider.d.ts +16 -0
- package/dist/edit/docx/provider.js +37 -0
- package/dist/edit/docx/schemas.d.ts +6 -0
- package/dist/edit/docx/schemas.js +192 -0
- package/dist/edit/docx/session.d.ts +48 -0
- package/dist/edit/docx/session.js +467 -0
- package/dist/edit/docx/structure-ops.d.ts +6 -0
- package/dist/edit/docx/structure-ops.js +529 -0
- package/dist/edit/docx/style.d.ts +45 -0
- package/dist/edit/docx/style.js +375 -0
- package/dist/edit/docx/table-ops.d.ts +4 -0
- package/dist/edit/docx/table-ops.js +241 -0
- package/dist/edit/docx/text-ops.d.ts +40 -0
- package/dist/edit/docx/text-ops.js +491 -0
- package/dist/edit/docx/text.d.ts +34 -0
- package/dist/edit/docx/text.js +284 -0
- package/dist/edit/docx/tracked.d.ts +52 -0
- package/dist/edit/docx/tracked.js +347 -0
- package/dist/edit/docx/types.d.ts +206 -0
- package/dist/edit/docx/types.js +1 -0
- package/dist/edit/docx/write.d.ts +64 -0
- package/dist/edit/docx/write.js +375 -0
- package/dist/edit/engine.d.ts +128 -0
- package/dist/edit/engine.js +1 -0
- package/dist/edit/history.d.ts +77 -0
- package/dist/edit/history.js +123 -0
- package/dist/edit/ooxml/names.d.ts +20 -0
- package/dist/edit/ooxml/names.js +61 -0
- package/dist/edit/ooxml/opc.d.ts +50 -0
- package/dist/edit/ooxml/opc.js +150 -0
- package/dist/edit/ooxml/package.d.ts +82 -0
- package/dist/edit/ooxml/package.js +233 -0
- package/dist/edit/ooxml/patch.d.ts +51 -0
- package/dist/edit/ooxml/patch.js +250 -0
- package/dist/edit/ooxml/transaction.d.ts +39 -0
- package/dist/edit/ooxml/transaction.js +316 -0
- package/dist/edit/ooxml/worker.d.ts +8 -0
- package/dist/edit/ooxml/worker.js +12 -0
- package/dist/edit/ooxml/writer.d.ts +21 -0
- package/dist/edit/ooxml/writer.js +187 -0
- package/dist/edit/ooxml/xml.d.ts +74 -0
- package/dist/edit/ooxml/xml.js +451 -0
- package/dist/edit/ooxml/zip.d.ts +54 -0
- package/dist/edit/ooxml/zip.js +280 -0
- package/dist/edit/operations.d.ts +19 -0
- package/dist/edit/operations.js +137 -0
- package/dist/edit/pdf/engine/compact.d.ts +6 -0
- package/dist/edit/pdf/engine/compact.js +442 -0
- package/dist/edit/pdf/engine/document.d.ts +95 -0
- package/dist/edit/pdf/engine/document.js +868 -0
- package/dist/edit/pdf/engine/elements.d.ts +45 -0
- package/dist/edit/pdf/engine/elements.js +313 -0
- package/dist/edit/pdf/engine/existing-text.d.ts +4 -0
- package/dist/edit/pdf/engine/existing-text.js +424 -0
- package/dist/edit/pdf/engine/fonts.d.ts +78 -0
- package/dist/edit/pdf/engine/fonts.js +466 -0
- package/dist/edit/pdf/engine/geometry.d.ts +36 -0
- package/dist/edit/pdf/engine/geometry.js +97 -0
- package/dist/edit/pdf/engine/handler.d.ts +21 -0
- package/dist/edit/pdf/engine/handler.js +108 -0
- package/dist/edit/pdf/engine/images.d.ts +33 -0
- package/dist/edit/pdf/engine/images.js +188 -0
- package/dist/edit/pdf/engine/layout.d.ts +23 -0
- package/dist/edit/pdf/engine/layout.js +177 -0
- package/dist/edit/pdf/engine/operations.d.ts +67 -0
- package/dist/edit/pdf/engine/operations.js +3 -0
- package/dist/edit/pdf/engine/pages.d.ts +6 -0
- package/dist/edit/pdf/engine/pages.js +98 -0
- package/dist/edit/pdf/engine/pdfium.d.ts +195 -0
- package/dist/edit/pdf/engine/pdfium.js +249 -0
- package/dist/edit/pdf/engine/shapes.d.ts +4 -0
- package/dist/edit/pdf/engine/shapes.js +183 -0
- package/dist/edit/pdf/engine/tables.d.ts +44 -0
- package/dist/edit/pdf/engine/tables.js +315 -0
- package/dist/edit/pdf/engine/text-box.d.ts +71 -0
- package/dist/edit/pdf/engine/text-box.js +317 -0
- package/dist/edit/pdf/engine/text-layout.d.ts +28 -0
- package/dist/edit/pdf/engine/text-layout.js +67 -0
- package/dist/edit/pdf/engine/transform.d.ts +5 -0
- package/dist/edit/pdf/engine/transform.js +137 -0
- package/dist/edit/pdf/provider.d.ts +35 -0
- package/dist/edit/pdf/provider.js +92 -0
- package/dist/edit/pdf/range-map.d.ts +16 -0
- package/dist/edit/pdf/range-map.js +107 -0
- package/dist/edit/pdf/schemas.d.ts +11 -0
- package/dist/edit/pdf/schemas.js +291 -0
- package/dist/edit/pdf/selection.d.ts +5 -0
- package/dist/edit/pdf/selection.js +152 -0
- package/dist/edit/pdf/session.d.ts +63 -0
- package/dist/edit/pdf/session.js +312 -0
- package/dist/edit/pdf/types.d.ts +336 -0
- package/dist/edit/pdf/types.js +1 -0
- package/dist/edit/pptx/elements.d.ts +74 -0
- package/dist/edit/pptx/elements.js +301 -0
- package/dist/edit/pptx/engine.d.ts +37 -0
- package/dist/edit/pptx/engine.js +466 -0
- package/dist/edit/pptx/geometry.d.ts +72 -0
- package/dist/edit/pptx/geometry.js +193 -0
- package/dist/edit/pptx/handler.d.ts +7 -0
- package/dist/edit/pptx/handler.js +99 -0
- package/dist/edit/pptx/handlers.d.ts +3 -0
- package/dist/edit/pptx/handlers.js +21 -0
- package/dist/edit/pptx/image-table-ops.d.ts +5 -0
- package/dist/edit/pptx/image-table-ops.js +233 -0
- package/dist/edit/pptx/model.d.ts +69 -0
- package/dist/edit/pptx/model.js +170 -0
- package/dist/edit/pptx/operations.d.ts +50 -0
- package/dist/edit/pptx/operations.js +11 -0
- package/dist/edit/pptx/provider.d.ts +17 -0
- package/dist/edit/pptx/provider.js +40 -0
- package/dist/edit/pptx/schemas.d.ts +6 -0
- package/dist/edit/pptx/schemas.js +220 -0
- package/dist/edit/pptx/session.d.ts +54 -0
- package/dist/edit/pptx/session.js +145 -0
- package/dist/edit/pptx/shape-ops.d.ts +10 -0
- package/dist/edit/pptx/shape-ops.js +486 -0
- package/dist/edit/pptx/slide-ops.d.ts +6 -0
- package/dist/edit/pptx/slide-ops.js +353 -0
- package/dist/edit/pptx/style.d.ts +31 -0
- package/dist/edit/pptx/style.js +181 -0
- package/dist/edit/pptx/text-ops.d.ts +13 -0
- package/dist/edit/pptx/text-ops.js +267 -0
- package/dist/edit/pptx/text-write.d.ts +67 -0
- package/dist/edit/pptx/text-write.js +293 -0
- package/dist/edit/pptx/text.d.ts +41 -0
- package/dist/edit/pptx/text.js +99 -0
- package/dist/edit/pptx/types.d.ts +232 -0
- package/dist/edit/pptx/types.js +1 -0
- package/dist/edit/schema.d.ts +11 -0
- package/dist/edit/schema.js +275 -0
- package/dist/edit/session.d.ts +77 -0
- package/dist/edit/session.js +943 -0
- package/dist/edit/sessions.d.ts +8 -0
- package/dist/edit/sessions.js +1 -0
- package/dist/edit/types.d.ts +290 -0
- package/dist/edit/types.js +1 -0
- package/dist/edit/worker-engine.d.ts +31 -0
- package/dist/edit/worker-engine.js +91 -0
- package/dist/fonts/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/fonts/manifest.json +6 -1
- package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
- package/dist/fonts.d.ts +2 -0
- package/dist/fonts.js +4 -0
- package/dist/fuzzy-alignment.d.ts +11 -4
- package/dist/fuzzy-alignment.js +3 -9
- package/dist/headless.d.ts +5 -1
- package/dist/headless.js +8 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +16 -2
- package/dist/limits.js +6 -0
- package/dist/ooxml-edit-worker.d.ts +1 -0
- package/dist/ooxml-edit-worker.js +8 -0
- package/dist/pdf-edit-worker.d.ts +1 -0
- package/dist/pdf-edit-worker.js +35 -0
- package/dist/spreadsheet-viewport.d.ts +5 -0
- package/dist/spreadsheet-viewport.js +11 -0
- package/dist/ui.js +7 -0
- package/dist/viewer.d.ts +6 -0
- package/dist/viewer.js +293 -19
- package/dist/viewport.d.ts +13 -0
- package/dist/viewport.js +126 -1
- package/dist/worker-protocol.d.ts +39 -2
- package/dist/workers/fuzzy-search-worker.js +1 -1
- package/dist/workers/ooxml-edit-worker.js +9212 -0
- package/dist/workers/pdf-edit-worker.js +10847 -0
- package/package.json +3 -3
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { ViewerError } from "../../errors.js";
|
|
2
|
+
export const METHOD_STORED = 0;
|
|
3
|
+
export const METHOD_DEFLATED = 8;
|
|
4
|
+
const SIGNATURE_LOCAL = 0x04034b50;
|
|
5
|
+
const SIGNATURE_CENTRAL = 0x02014b50;
|
|
6
|
+
const SIGNATURE_EOCD = 0x06054b50;
|
|
7
|
+
const SIGNATURE_DESCRIPTOR = 0x08074b50;
|
|
8
|
+
const SIGNATURE_ZIP64_LOCATOR = 0x07064b50;
|
|
9
|
+
const EOCD_LENGTH = 22;
|
|
10
|
+
const MAX_COMMENT = 0xffff;
|
|
11
|
+
const FLAG_ENCRYPTED = 0x0001;
|
|
12
|
+
const FLAG_DESCRIPTOR = 0x0008;
|
|
13
|
+
const FLAG_STRONG_ENCRYPTION = 0x0040;
|
|
14
|
+
const ZIP64_MARKER = 0xffffffff;
|
|
15
|
+
/** Parses the end-of-central-directory record and the central directory. */
|
|
16
|
+
export function parseZip(bytes, limits) {
|
|
17
|
+
if (bytes.byteLength > limits.maxInputBytes)
|
|
18
|
+
throw limit("Input exceeds maxInputBytes", bytes.byteLength, limits.maxInputBytes);
|
|
19
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
20
|
+
const eocdOffset = findEndOfCentralDirectory(bytes, view);
|
|
21
|
+
if (eocdOffset === undefined)
|
|
22
|
+
throw invalid("No end-of-central-directory record");
|
|
23
|
+
if (eocdOffset >= 20 &&
|
|
24
|
+
view.getUint32(eocdOffset - 20, true) === SIGNATURE_ZIP64_LOCATOR)
|
|
25
|
+
throw unsupported("ZIP64 archives are not supported");
|
|
26
|
+
const thisDisk = view.getUint16(eocdOffset + 4, true);
|
|
27
|
+
const directoryDisk = view.getUint16(eocdOffset + 6, true);
|
|
28
|
+
const countOnDisk = view.getUint16(eocdOffset + 8, true);
|
|
29
|
+
const count = view.getUint16(eocdOffset + 10, true);
|
|
30
|
+
const centralDirectoryLength = view.getUint32(eocdOffset + 12, true);
|
|
31
|
+
const centralDirectoryOffset = view.getUint32(eocdOffset + 16, true);
|
|
32
|
+
const commentLength = view.getUint16(eocdOffset + 20, true);
|
|
33
|
+
if (thisDisk !== 0 || directoryDisk !== 0 || countOnDisk !== count)
|
|
34
|
+
throw unsupported("Multi-disk archives are not supported");
|
|
35
|
+
if (count === 0xffff ||
|
|
36
|
+
centralDirectoryLength === ZIP64_MARKER ||
|
|
37
|
+
centralDirectoryOffset === ZIP64_MARKER)
|
|
38
|
+
throw unsupported("ZIP64 archives are not supported");
|
|
39
|
+
if (centralDirectoryOffset + centralDirectoryLength > eocdOffset)
|
|
40
|
+
throw invalid("The central directory runs past its end record");
|
|
41
|
+
const comment = bytes.subarray(eocdOffset + EOCD_LENGTH, eocdOffset + EOCD_LENGTH + commentLength);
|
|
42
|
+
const entries = [];
|
|
43
|
+
let offset = centralDirectoryOffset;
|
|
44
|
+
let expanded = 0;
|
|
45
|
+
for (let index = 0; index < count; index += 1) {
|
|
46
|
+
if (offset + 46 > eocdOffset ||
|
|
47
|
+
view.getUint32(offset, true) !== SIGNATURE_CENTRAL)
|
|
48
|
+
throw invalid(`Central-directory entry ${index} is missing or malformed`);
|
|
49
|
+
const versionMadeBy = view.getUint16(offset + 4, true);
|
|
50
|
+
const versionNeeded = view.getUint16(offset + 6, true);
|
|
51
|
+
const flags = view.getUint16(offset + 8, true);
|
|
52
|
+
const method = view.getUint16(offset + 10, true);
|
|
53
|
+
const modTime = view.getUint16(offset + 12, true);
|
|
54
|
+
const modDate = view.getUint16(offset + 14, true);
|
|
55
|
+
const crc32 = view.getUint32(offset + 16, true);
|
|
56
|
+
const compressedSize = view.getUint32(offset + 20, true);
|
|
57
|
+
const uncompressedSize = view.getUint32(offset + 24, true);
|
|
58
|
+
const nameLength = view.getUint16(offset + 28, true);
|
|
59
|
+
const extraLength = view.getUint16(offset + 30, true);
|
|
60
|
+
const commentLength = view.getUint16(offset + 32, true);
|
|
61
|
+
const diskNumber = view.getUint16(offset + 34, true);
|
|
62
|
+
const internalAttributes = view.getUint16(offset + 36, true);
|
|
63
|
+
const externalAttributes = view.getUint32(offset + 38, true);
|
|
64
|
+
const localHeaderOffset = view.getUint32(offset + 42, true);
|
|
65
|
+
const centralLength = 46 + nameLength + extraLength + commentLength;
|
|
66
|
+
if (offset + centralLength > eocdOffset)
|
|
67
|
+
throw invalid(`Central-directory entry ${index} runs past the directory`);
|
|
68
|
+
if (compressedSize === ZIP64_MARKER ||
|
|
69
|
+
uncompressedSize === ZIP64_MARKER ||
|
|
70
|
+
localHeaderOffset === ZIP64_MARKER)
|
|
71
|
+
throw unsupported("ZIP64 entries are not supported");
|
|
72
|
+
if (diskNumber !== 0)
|
|
73
|
+
throw unsupported("Multi-disk archives are not supported");
|
|
74
|
+
if (flags & (FLAG_ENCRYPTED | FLAG_STRONG_ENCRYPTION))
|
|
75
|
+
throw unsupported("Encrypted archives are not supported");
|
|
76
|
+
if (method !== METHOD_STORED && method !== METHOD_DEFLATED)
|
|
77
|
+
throw unsupported(`Compression method ${method} is not supported`);
|
|
78
|
+
if (uncompressedSize > limits.maxZipEntryBytes)
|
|
79
|
+
throw limit("ZIP entry exceeds maxZipEntryBytes", uncompressedSize, limits.maxZipEntryBytes);
|
|
80
|
+
expanded += uncompressedSize;
|
|
81
|
+
if (expanded > limits.maxExpandedOfficeBytes)
|
|
82
|
+
throw limit("Office package exceeds maxExpandedOfficeBytes", expanded, limits.maxExpandedOfficeBytes);
|
|
83
|
+
if (localHeaderOffset + 30 > centralDirectoryOffset)
|
|
84
|
+
throw invalid(`Entry ${index} points outside the archive`);
|
|
85
|
+
const nameBytes = bytes.subarray(offset + 46, offset + 46 + nameLength);
|
|
86
|
+
entries.push({
|
|
87
|
+
name: new TextDecoder("utf-8", { fatal: false }).decode(nameBytes),
|
|
88
|
+
nameBytes,
|
|
89
|
+
method,
|
|
90
|
+
flags,
|
|
91
|
+
crc32,
|
|
92
|
+
compressedSize,
|
|
93
|
+
uncompressedSize,
|
|
94
|
+
localHeaderOffset,
|
|
95
|
+
centralOffset: offset,
|
|
96
|
+
centralLength,
|
|
97
|
+
extra: bytes.subarray(offset + 46 + nameLength, offset + 46 + nameLength + extraLength),
|
|
98
|
+
comment: bytes.subarray(offset + 46 + nameLength + extraLength, offset + centralLength),
|
|
99
|
+
modTime,
|
|
100
|
+
modDate,
|
|
101
|
+
versionMadeBy,
|
|
102
|
+
versionNeeded,
|
|
103
|
+
internalAttributes,
|
|
104
|
+
externalAttributes,
|
|
105
|
+
});
|
|
106
|
+
offset += centralLength;
|
|
107
|
+
}
|
|
108
|
+
if (offset !== centralDirectoryOffset + centralDirectoryLength)
|
|
109
|
+
throw invalid("The central directory's length does not match its entries");
|
|
110
|
+
return {
|
|
111
|
+
bytes,
|
|
112
|
+
entries,
|
|
113
|
+
centralDirectoryOffset,
|
|
114
|
+
centralDirectoryLength,
|
|
115
|
+
eocdOffset,
|
|
116
|
+
comment,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Locates an entry's local record and checks it against the central record:
|
|
121
|
+
* same name, same method, and the same sizes and CRC unless the entry
|
|
122
|
+
* carries them in a data descriptor instead.
|
|
123
|
+
*/
|
|
124
|
+
export function localRecordOf(archive, entry) {
|
|
125
|
+
const { bytes } = archive;
|
|
126
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
127
|
+
const headerOffset = entry.localHeaderOffset;
|
|
128
|
+
if (headerOffset + 30 > bytes.byteLength ||
|
|
129
|
+
view.getUint32(headerOffset, true) !== SIGNATURE_LOCAL)
|
|
130
|
+
throw invalid(`Entry ${entry.name} has no local header`);
|
|
131
|
+
const flags = view.getUint16(headerOffset + 6, true);
|
|
132
|
+
const method = view.getUint16(headerOffset + 8, true);
|
|
133
|
+
const crc32 = view.getUint32(headerOffset + 14, true);
|
|
134
|
+
const compressedSize = view.getUint32(headerOffset + 18, true);
|
|
135
|
+
const uncompressedSize = view.getUint32(headerOffset + 22, true);
|
|
136
|
+
const nameLength = view.getUint16(headerOffset + 26, true);
|
|
137
|
+
const extraLength = view.getUint16(headerOffset + 28, true);
|
|
138
|
+
const dataStart = headerOffset + 30 + nameLength + extraLength;
|
|
139
|
+
const localName = bytes.subarray(headerOffset + 30, headerOffset + 30 + nameLength);
|
|
140
|
+
if (!sameBytes(localName, entry.nameBytes))
|
|
141
|
+
throw invalid(`Entry ${entry.name} is named differently in its local header`);
|
|
142
|
+
if (method !== entry.method)
|
|
143
|
+
throw invalid(`Entry ${entry.name} has a different method in its local header`);
|
|
144
|
+
const hasDescriptor = (flags & FLAG_DESCRIPTOR) !== 0;
|
|
145
|
+
if (!hasDescriptor &&
|
|
146
|
+
(crc32 !== entry.crc32 ||
|
|
147
|
+
compressedSize !== entry.compressedSize ||
|
|
148
|
+
uncompressedSize !== entry.uncompressedSize))
|
|
149
|
+
throw invalid(`Entry ${entry.name} has different sizes in its local header`);
|
|
150
|
+
const dataEnd = dataStart + entry.compressedSize;
|
|
151
|
+
if (dataEnd > archive.centralDirectoryOffset)
|
|
152
|
+
throw invalid(`Entry ${entry.name} runs into the central directory`);
|
|
153
|
+
let recordEnd = dataEnd;
|
|
154
|
+
if (hasDescriptor) {
|
|
155
|
+
let at = dataEnd;
|
|
156
|
+
if (at + 4 <= bytes.byteLength &&
|
|
157
|
+
view.getUint32(at, true) === SIGNATURE_DESCRIPTOR)
|
|
158
|
+
at += 4;
|
|
159
|
+
if (at + 12 > bytes.byteLength)
|
|
160
|
+
throw invalid(`Entry ${entry.name} has a truncated data descriptor`);
|
|
161
|
+
const descriptorCrc = view.getUint32(at, true);
|
|
162
|
+
const descriptorCompressed = view.getUint32(at + 4, true);
|
|
163
|
+
const descriptorUncompressed = view.getUint32(at + 8, true);
|
|
164
|
+
if (descriptorCrc !== entry.crc32 ||
|
|
165
|
+
descriptorCompressed !== entry.compressedSize ||
|
|
166
|
+
descriptorUncompressed !== entry.uncompressedSize)
|
|
167
|
+
throw invalid(`Entry ${entry.name} has a data descriptor that disagrees with the directory`);
|
|
168
|
+
recordEnd = at + 12;
|
|
169
|
+
}
|
|
170
|
+
return { headerOffset, dataStart, dataEnd, recordEnd };
|
|
171
|
+
}
|
|
172
|
+
/** The entry's bytes, inflated when needed, metered and CRC-checked. */
|
|
173
|
+
export async function inflateEntry(archive, entry, limits, signal) {
|
|
174
|
+
const local = localRecordOf(archive, entry);
|
|
175
|
+
const data = archive.bytes.subarray(local.dataStart, local.dataEnd);
|
|
176
|
+
let output;
|
|
177
|
+
if (entry.method === METHOD_STORED) {
|
|
178
|
+
if (data.byteLength !== entry.uncompressedSize)
|
|
179
|
+
throw invalid(`Entry ${entry.name} is stored with a different size than declared`);
|
|
180
|
+
output = data.slice();
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
output = await inflateRaw(data, entry, limits, signal);
|
|
184
|
+
}
|
|
185
|
+
if (crc32(output) !== entry.crc32)
|
|
186
|
+
throw invalid(`Entry ${entry.name} does not match its CRC-32`);
|
|
187
|
+
return output;
|
|
188
|
+
}
|
|
189
|
+
async function inflateRaw(data, entry, limits, signal) {
|
|
190
|
+
if (typeof DecompressionStream !== "function")
|
|
191
|
+
throw new ViewerError("edit-unsupported", "This environment has no DecompressionStream for OOXML parts", { details: { reason: "no-decompression-stream" } });
|
|
192
|
+
const expected = entry.uncompressedSize;
|
|
193
|
+
const ceiling = Math.min(expected, limits.maxZipEntryBytes);
|
|
194
|
+
const output = new Uint8Array(expected);
|
|
195
|
+
let total = 0;
|
|
196
|
+
const stream = new DecompressionStream("deflate-raw");
|
|
197
|
+
const writer = stream.writable.getWriter();
|
|
198
|
+
// A copy: the stream wants an ArrayBuffer-backed view, and the slice
|
|
199
|
+
// keeps a shared or detached source from leaking into the stream.
|
|
200
|
+
const writing = writer
|
|
201
|
+
.write(data.slice())
|
|
202
|
+
.then(() => writer.close())
|
|
203
|
+
.catch(() => undefined);
|
|
204
|
+
const reader = stream.readable.getReader();
|
|
205
|
+
try {
|
|
206
|
+
for (;;) {
|
|
207
|
+
if (signal?.aborted)
|
|
208
|
+
throw new ViewerError("aborted", "Inflation was aborted");
|
|
209
|
+
let chunk;
|
|
210
|
+
try {
|
|
211
|
+
chunk = await reader.read();
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
throw invalid(`Entry ${entry.name} is not valid deflate data`, error);
|
|
215
|
+
}
|
|
216
|
+
if (chunk.done)
|
|
217
|
+
break;
|
|
218
|
+
if (total + chunk.value.byteLength > ceiling) {
|
|
219
|
+
await reader.cancel().catch(() => undefined);
|
|
220
|
+
throw limit(`Entry ${entry.name} inflates beyond its declared size`, total + chunk.value.byteLength, ceiling);
|
|
221
|
+
}
|
|
222
|
+
output.set(chunk.value, total);
|
|
223
|
+
total += chunk.value.byteLength;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
reader.releaseLock();
|
|
228
|
+
await writing;
|
|
229
|
+
}
|
|
230
|
+
if (total !== expected)
|
|
231
|
+
throw invalid(`Entry ${entry.name} inflates to ${total} bytes, not the declared ${expected}`);
|
|
232
|
+
return output;
|
|
233
|
+
}
|
|
234
|
+
const CRC_TABLE = (() => {
|
|
235
|
+
const table = new Uint32Array(256);
|
|
236
|
+
for (let n = 0; n < 256; n += 1) {
|
|
237
|
+
let c = n;
|
|
238
|
+
for (let k = 0; k < 8; k += 1)
|
|
239
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
240
|
+
table[n] = c >>> 0;
|
|
241
|
+
}
|
|
242
|
+
return table;
|
|
243
|
+
})();
|
|
244
|
+
/** CRC-32 as ZIP uses it (IEEE 802.3, reflected). */
|
|
245
|
+
export function crc32(bytes, seed = 0) {
|
|
246
|
+
let crc = (seed ^ 0xffffffff) >>> 0;
|
|
247
|
+
for (let index = 0; index < bytes.byteLength; index += 1)
|
|
248
|
+
crc = CRC_TABLE[(crc ^ bytes[index]) & 0xff] ^ (crc >>> 8);
|
|
249
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
250
|
+
}
|
|
251
|
+
function findEndOfCentralDirectory(bytes, view) {
|
|
252
|
+
const floor = Math.max(0, bytes.byteLength - EOCD_LENGTH - MAX_COMMENT);
|
|
253
|
+
for (let offset = bytes.byteLength - EOCD_LENGTH; offset >= floor; offset -= 1) {
|
|
254
|
+
if (view.getUint32(offset, true) !== SIGNATURE_EOCD)
|
|
255
|
+
continue;
|
|
256
|
+
const commentLength = view.getUint16(offset + 20, true);
|
|
257
|
+
if (offset + EOCD_LENGTH + commentLength === bytes.byteLength)
|
|
258
|
+
return offset;
|
|
259
|
+
}
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
function sameBytes(a, b) {
|
|
263
|
+
if (a.byteLength !== b.byteLength)
|
|
264
|
+
return false;
|
|
265
|
+
for (let index = 0; index < a.byteLength; index += 1)
|
|
266
|
+
if (a[index] !== b[index])
|
|
267
|
+
return false;
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
function invalid(message, cause) {
|
|
271
|
+
return new ViewerError("invalid-file", message, cause === undefined ? {} : { cause });
|
|
272
|
+
}
|
|
273
|
+
function unsupported(message) {
|
|
274
|
+
return new ViewerError("unsupported-package", message);
|
|
275
|
+
}
|
|
276
|
+
function limit(message, actual, limit) {
|
|
277
|
+
return new ViewerError("resource-limit", message, {
|
|
278
|
+
details: { actual, limit },
|
|
279
|
+
});
|
|
280
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ViewerError } from "../errors.js";
|
|
2
|
+
import type { BinaryData, OperationIssue, OperationSchemaSet } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Checks the batch-level preconditions of `apply()`: an array that is not empty
|
|
5
|
+
* and not larger than the limit.
|
|
6
|
+
*/
|
|
7
|
+
export declare function assertBatchSize(operations: unknown, limit: number): void;
|
|
8
|
+
/**
|
|
9
|
+
* Shape and schema checks for every operation of a batch: JSON-only values, a
|
|
10
|
+
* known `op`, and the operation's JSON Schema. All issues are collected.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkOperations(operations: readonly unknown[], schemas: OperationSchemaSet): OperationIssue[];
|
|
13
|
+
export declare function invalidOperationError(issues: readonly OperationIssue[]): ViewerError;
|
|
14
|
+
/** Bytes of a `BinaryData` value that already passed schema validation. */
|
|
15
|
+
export declare function decodeBinary(data: BinaryData): Uint8Array;
|
|
16
|
+
/** Deep copy of a validated batch, so later caller mutations cannot reach the history. */
|
|
17
|
+
/** The operation index a `"$<n>"` same-batch target names, if it is one. */
|
|
18
|
+
export declare function parseReference(target: string): number | undefined;
|
|
19
|
+
export declare function freezeOperations<T>(operations: readonly T[]): readonly T[];
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { ViewerError } from "../errors.js";
|
|
2
|
+
import { pointerSegment, validateSchema } from "./schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Checks the batch-level preconditions of `apply()`: an array that is not empty
|
|
5
|
+
* and not larger than the limit.
|
|
6
|
+
*/
|
|
7
|
+
export function assertBatchSize(operations, limit) {
|
|
8
|
+
if (!Array.isArray(operations) || operations.length === 0)
|
|
9
|
+
throw invalidOperationError([
|
|
10
|
+
{
|
|
11
|
+
operationIndex: -1,
|
|
12
|
+
path: "",
|
|
13
|
+
code: "empty-batch",
|
|
14
|
+
message: "A batch needs at least one operation",
|
|
15
|
+
},
|
|
16
|
+
]);
|
|
17
|
+
if (operations.length > limit)
|
|
18
|
+
throw new ViewerError("resource-limit", "The batch exceeds maxEditOperations", { details: { operations: operations.length, limit } });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Shape and schema checks for every operation of a batch: JSON-only values, a
|
|
22
|
+
* known `op`, and the operation's JSON Schema. All issues are collected.
|
|
23
|
+
*/
|
|
24
|
+
export function checkOperations(operations, schemas) {
|
|
25
|
+
const issues = [];
|
|
26
|
+
operations.forEach((operation, operationIndex) => {
|
|
27
|
+
const shape = jsonIssues(operation, "", operationIndex, new Set());
|
|
28
|
+
if (shape.length > 0) {
|
|
29
|
+
issues.push(...shape);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!isPlainObject(operation) || typeof operation.op !== "string") {
|
|
33
|
+
issues.push({
|
|
34
|
+
operationIndex,
|
|
35
|
+
path: "/op",
|
|
36
|
+
code: "required",
|
|
37
|
+
message: "An operation is an object with a string op",
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const schema = schemas.operations[operation.op];
|
|
42
|
+
if (!schema) {
|
|
43
|
+
issues.push({
|
|
44
|
+
operationIndex,
|
|
45
|
+
path: "/op",
|
|
46
|
+
code: "unknown-operation",
|
|
47
|
+
message: `Unknown ${schemas.format} operation ${operation.op}`,
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
issues.push(...validateSchema(operation, schema, operationIndex));
|
|
52
|
+
});
|
|
53
|
+
return issues;
|
|
54
|
+
}
|
|
55
|
+
export function invalidOperationError(issues) {
|
|
56
|
+
const first = issues[0];
|
|
57
|
+
return new ViewerError("invalid-operation", first
|
|
58
|
+
? `Invalid operation ${first.operationIndex} at ${first.path || "/"}: ${first.message}`
|
|
59
|
+
: "Invalid operation", {
|
|
60
|
+
details: {
|
|
61
|
+
issues: Object.freeze(issues.map((issue) => Object.freeze({ ...issue }))),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Bytes of a `BinaryData` value that already passed schema validation. */
|
|
66
|
+
export function decodeBinary(data) {
|
|
67
|
+
if (data instanceof Uint8Array)
|
|
68
|
+
return data;
|
|
69
|
+
return Uint8Array.from(atob(data), (character) => character.charCodeAt(0));
|
|
70
|
+
}
|
|
71
|
+
/** Deep copy of a validated batch, so later caller mutations cannot reach the history. */
|
|
72
|
+
/** The operation index a `"$<n>"` same-batch target names, if it is one. */
|
|
73
|
+
export function parseReference(target) {
|
|
74
|
+
const match = /^\$(\d{1,4})$/.exec(target);
|
|
75
|
+
return match ? Number(match[1]) : undefined;
|
|
76
|
+
}
|
|
77
|
+
export function freezeOperations(operations) {
|
|
78
|
+
return Object.freeze(operations.map((operation) => deepCopy(operation)));
|
|
79
|
+
}
|
|
80
|
+
function deepCopy(value) {
|
|
81
|
+
if (value instanceof Uint8Array)
|
|
82
|
+
return value.slice();
|
|
83
|
+
if (Array.isArray(value))
|
|
84
|
+
return Object.freeze(value.map(deepCopy));
|
|
85
|
+
if (isPlainObject(value))
|
|
86
|
+
return Object.freeze(Object.fromEntries(Object.entries(value)
|
|
87
|
+
.filter(([, child]) => child !== undefined)
|
|
88
|
+
.map(([key, child]) => [key, deepCopy(child)])));
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function jsonIssues(value, path, operationIndex, ancestors) {
|
|
92
|
+
const issue = (message) => [
|
|
93
|
+
{ operationIndex, path, code: "not-json", message },
|
|
94
|
+
];
|
|
95
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
96
|
+
return [];
|
|
97
|
+
if (typeof value === "number")
|
|
98
|
+
return Number.isFinite(value) ? [] : issue("Numbers must be finite");
|
|
99
|
+
if (value instanceof Uint8Array)
|
|
100
|
+
return [];
|
|
101
|
+
if (typeof value !== "object")
|
|
102
|
+
return issue(`A ${typeof value} is not a JSON value`);
|
|
103
|
+
if (ancestors.has(value))
|
|
104
|
+
return issue("Cyclic value");
|
|
105
|
+
const issues = [];
|
|
106
|
+
ancestors.add(value);
|
|
107
|
+
if (Array.isArray(value)) {
|
|
108
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
109
|
+
const childPath = `${path}${pointerSegment(index)}`;
|
|
110
|
+
if (!(index in value) || value[index] === undefined)
|
|
111
|
+
issues.push({
|
|
112
|
+
operationIndex,
|
|
113
|
+
path: childPath,
|
|
114
|
+
code: "not-json",
|
|
115
|
+
message: "Arrays cannot hold holes or undefined",
|
|
116
|
+
});
|
|
117
|
+
else
|
|
118
|
+
issues.push(...jsonIssues(value[index], childPath, operationIndex, ancestors));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (isPlainObject(value)) {
|
|
122
|
+
for (const [key, child] of Object.entries(value))
|
|
123
|
+
if (child !== undefined)
|
|
124
|
+
issues.push(...jsonIssues(child, `${path}${pointerSegment(key)}`, operationIndex, ancestors));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
issues.push(...issue("Class instances are not JSON values"));
|
|
128
|
+
}
|
|
129
|
+
ancestors.delete(value);
|
|
130
|
+
return issues;
|
|
131
|
+
}
|
|
132
|
+
function isPlainObject(value) {
|
|
133
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
134
|
+
return false;
|
|
135
|
+
const prototype = Object.getPrototypeOf(value);
|
|
136
|
+
return prototype === Object.prototype || prototype === null;
|
|
137
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ViewerError } from "../../../errors.js";
|
|
2
|
+
export declare class PdfCompactionError extends ViewerError {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/** The reachable objects of `bytes` with a fresh cross-reference table. */
|
|
6
|
+
export declare function compactPdf(bytes: Uint8Array): Uint8Array;
|