archive-codec 1.2.0 → 1.4.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.
@@ -0,0 +1,295 @@
1
+ //#region src/cfb/write.ts
2
+ const ENDOFCHAIN = 4294967294;
3
+ const FATSECT = 4294967293;
4
+ const DIFSECT = 4294967292;
5
+ const NOSTREAM = 4294967295;
6
+ const FREESECT_FILL_BYTE = 255;
7
+ const HEADER_DIFAT_ENTRIES = 109;
8
+ const HEADER_DIFAT_OFFSET = 76;
9
+ const DIRECTORY_ENTRY_SIZE = 128;
10
+ const MAX_NAME_CODE_UNITS = 31;
11
+ const MINI_SECTOR_SHIFT = 6;
12
+ const MINI_SECTOR_SIZE = 64;
13
+ const MINI_STREAM_CUTOFF = 4096;
14
+ const OBJECT_TYPE_STORAGE = 1;
15
+ const OBJECT_TYPE_STREAM = 2;
16
+ const OBJECT_TYPE_ROOT = 5;
17
+ const COLOUR_RED = 0;
18
+ const COLOUR_BLACK = 1;
19
+ const ROOT_ENTRY_NAME = "Root Entry";
20
+ const MAX_VERSION_3_STREAM_BYTES = 2147483648;
21
+ const ILLEGAL_NAME_CHARACTERS = [
22
+ "\\",
23
+ ":",
24
+ "!"
25
+ ];
26
+ var CompoundFileWriteError = class extends Error {
27
+ constructor(message) {
28
+ super(message);
29
+ this.name = "CompoundFileWriteError";
30
+ }
31
+ };
32
+ function isStorage(node) {
33
+ return "children" in node;
34
+ }
35
+ function objectTypeOf(entry) {
36
+ if (entry.id === 0) return OBJECT_TYPE_ROOT;
37
+ return isStorage(entry.node) ? OBJECT_TYPE_STORAGE : OBJECT_TYPE_STREAM;
38
+ }
39
+ function upperCodeUnit(value, index) {
40
+ const unit = value.charCodeAt(index);
41
+ if (unit >= 55296 && unit <= 57343) return unit;
42
+ const upper = String.fromCharCode(unit).toUpperCase();
43
+ return upper.length === 1 ? upper.charCodeAt(0) : unit;
44
+ }
45
+ function compareEntryNames(left, right) {
46
+ if (left.length !== right.length) return left.length - right.length;
47
+ for (let i = 0; i < left.length; i++) {
48
+ const difference = upperCodeUnit(left, i) - upperCodeUnit(right, i);
49
+ if (difference !== 0) return difference;
50
+ }
51
+ return 0;
52
+ }
53
+ function checkedSegment(name, path) {
54
+ if (name.length === 0) throw new CompoundFileWriteError(`stream path ${JSON.stringify(path)} has an empty name segment; every segment must name a storage, and the last must name the stream`);
55
+ if (name.length > MAX_NAME_CODE_UNITS) throw new CompoundFileWriteError(`'${name}' is ${name.length} UTF-16 code points, more than the ${MAX_NAME_CODE_UNITS} a directory entry's name field holds alongside its terminating null (in stream path ${JSON.stringify(path)})`);
56
+ for (const illegal of ILLEGAL_NAME_CHARACTERS) if (name.includes(illegal)) throw new CompoundFileWriteError(`'${name}' holds '${illegal}', which [MS-CFB] 2.6.1 forbids in a storage or stream name (in stream path ${JSON.stringify(path)})`);
57
+ return name;
58
+ }
59
+ function addStream(root, path, bytes) {
60
+ const segments = path.split("/");
61
+ let storage = root;
62
+ let depth = 0;
63
+ for (const segment of segments) {
64
+ depth += 1;
65
+ const name = checkedSegment(segment, path);
66
+ const existing = storage.children.find((child) => compareEntryNames(child.name, name) === 0);
67
+ if (depth === segments.length) {
68
+ if (existing !== void 0) throw new CompoundFileWriteError(`stream path ${JSON.stringify(path)} collides with '${existing.name}', which the file already holds in the same storage ([MS-CFB] 2.6.4 requires siblings to have unique names)`);
69
+ storage.children.push({
70
+ name,
71
+ bytes
72
+ });
73
+ } else if (existing === void 0) {
74
+ const created = {
75
+ name,
76
+ children: []
77
+ };
78
+ storage.children.push(created);
79
+ storage = created;
80
+ } else if (isStorage(existing)) storage = existing;
81
+ else throw new CompoundFileWriteError(`stream path ${JSON.stringify(path)} needs '${existing.name}' to be a storage, but the file already holds a stream by that name`);
82
+ }
83
+ }
84
+ function deepestDepth(count) {
85
+ return count === 0 ? 0 : 31 - Math.clz32(count);
86
+ }
87
+ function linkSiblings(siblings, depth, deepest) {
88
+ const midpoint = siblings.length >> 1;
89
+ const before = siblings.slice(0, midpoint);
90
+ const [node, ...after] = siblings.slice(midpoint);
91
+ if (node === void 0) return;
92
+ node.colour = depth === deepest && deepest > 0 ? COLOUR_RED : COLOUR_BLACK;
93
+ const left = linkSiblings(before, depth + 1, deepest);
94
+ const right = linkSiblings(after, depth + 1, deepest);
95
+ node.left = left === void 0 ? NOSTREAM : left.id;
96
+ node.right = right === void 0 ? NOSTREAM : right.id;
97
+ return node;
98
+ }
99
+ function planDirectory(root) {
100
+ const plans = [];
101
+ const plan = (node) => {
102
+ const created = {
103
+ id: plans.length,
104
+ node,
105
+ left: NOSTREAM,
106
+ right: NOSTREAM,
107
+ child: NOSTREAM,
108
+ colour: COLOUR_BLACK,
109
+ startSector: 0,
110
+ size: 0
111
+ };
112
+ plans.push(created);
113
+ return created;
114
+ };
115
+ const rootPlan = plan(root);
116
+ let frontier = [rootPlan];
117
+ while (frontier.length > 0) {
118
+ const next = [];
119
+ for (const parent of frontier) {
120
+ const node = parent.node;
121
+ if (!isStorage(node)) continue;
122
+ node.children.sort((left, right) => compareEntryNames(left.name, right.name));
123
+ const children = [];
124
+ for (const child of node.children) {
125
+ const childPlan = plan(child);
126
+ children.push(childPlan);
127
+ next.push(childPlan);
128
+ }
129
+ const subtree = linkSiblings(children, 0, deepestDepth(children.length));
130
+ parent.child = subtree === void 0 ? NOSTREAM : subtree.id;
131
+ }
132
+ frontier = next;
133
+ }
134
+ return {
135
+ rootPlan,
136
+ plans
137
+ };
138
+ }
139
+ function writeCompoundFile(streams, options = {}) {
140
+ const majorVersion = options.majorVersion ?? 3;
141
+ const sectorShift = majorVersion === 4 ? 12 : 9;
142
+ const sectorSize = 1 << sectorShift;
143
+ const entriesPerFatSector = sectorSize / 4;
144
+ const entriesPerDirectorySector = sectorSize / DIRECTORY_ENTRY_SIZE;
145
+ const difatEntriesPerSector = entriesPerFatSector - 1;
146
+ const root = {
147
+ name: ROOT_ENTRY_NAME,
148
+ children: []
149
+ };
150
+ for (const { path, bytes } of streams) {
151
+ if (majorVersion === 3 && bytes.length > MAX_VERSION_3_STREAM_BYTES) throw new CompoundFileWriteError(`stream ${JSON.stringify(path)} is ${bytes.length} bytes, past the ${MAX_VERSION_3_STREAM_BYTES}-byte ceiling [MS-CFB] 2.6.1 puts on a version 3 stream; write the file as version 4 instead`);
152
+ addStream(root, path, bytes);
153
+ }
154
+ const { rootPlan, plans } = planDirectory(root);
155
+ const miniResident = [];
156
+ const fatResident = [];
157
+ for (const entry of plans) {
158
+ const node = entry.node;
159
+ if (isStorage(node)) continue;
160
+ entry.size = node.bytes.length;
161
+ if (node.bytes.length === 0) entry.startSector = ENDOFCHAIN;
162
+ else if (node.bytes.length < MINI_STREAM_CUTOFF) miniResident.push({
163
+ entry,
164
+ bytes: node.bytes
165
+ });
166
+ else fatResident.push({
167
+ entry,
168
+ bytes: node.bytes
169
+ });
170
+ }
171
+ let miniSectorCount = 0;
172
+ for (const { entry, bytes } of miniResident) {
173
+ entry.startSector = miniSectorCount;
174
+ miniSectorCount += Math.ceil(bytes.length / MINI_SECTOR_SIZE);
175
+ }
176
+ const miniStreamBytes = miniSectorCount * MINI_SECTOR_SIZE;
177
+ const directorySectorCount = Math.ceil(plans.length / entriesPerDirectorySector);
178
+ const miniStreamSectorCount = Math.ceil(miniStreamBytes / sectorSize);
179
+ const miniFatSectorCount = Math.ceil(miniSectorCount / entriesPerFatSector);
180
+ let fatStreamSectorCount = 0;
181
+ for (const { bytes } of fatResident) fatStreamSectorCount += Math.ceil(bytes.length / sectorSize);
182
+ const totalSectorsGiven = (fat, difat) => fat + difat + directorySectorCount + fatStreamSectorCount + miniStreamSectorCount + miniFatSectorCount;
183
+ let fatSectorCount = 1;
184
+ let difatSectorCount = 0;
185
+ for (;;) {
186
+ const neededFat = Math.max(1, Math.ceil(totalSectorsGiven(fatSectorCount, difatSectorCount) / entriesPerFatSector));
187
+ const neededDifat = neededFat <= HEADER_DIFAT_ENTRIES ? 0 : Math.ceil((neededFat - HEADER_DIFAT_ENTRIES) / difatEntriesPerSector);
188
+ if (neededFat === fatSectorCount && neededDifat === difatSectorCount) break;
189
+ fatSectorCount = neededFat;
190
+ difatSectorCount = neededDifat;
191
+ }
192
+ const totalSectors = totalSectorsGiven(fatSectorCount, difatSectorCount);
193
+ const difatStart = fatSectorCount;
194
+ const directoryStart = difatStart + difatSectorCount;
195
+ let nextSector = directoryStart + directorySectorCount;
196
+ for (const { entry, bytes } of fatResident) {
197
+ entry.startSector = nextSector;
198
+ nextSector += Math.ceil(bytes.length / sectorSize);
199
+ }
200
+ const miniStreamStart = nextSector;
201
+ nextSector += miniStreamSectorCount;
202
+ const miniFatStart = nextSector;
203
+ rootPlan.startSector = miniSectorCount === 0 ? ENDOFCHAIN : miniStreamStart;
204
+ rootPlan.size = miniStreamBytes;
205
+ const file = new Uint8Array(sectorSize * (1 + totalSectors));
206
+ const view = new DataView(file.buffer);
207
+ const putU16 = (offset, value) => {
208
+ view.setUint16(offset, value, true);
209
+ };
210
+ const putU32 = (offset, value) => {
211
+ view.setUint32(offset, value, true);
212
+ };
213
+ const sectorOffset = (sector) => (sector + 1) * sectorSize;
214
+ file.fill(FREESECT_FILL_BYTE, sectorOffset(0), sectorOffset(0) + fatSectorCount * sectorSize);
215
+ file.fill(FREESECT_FILL_BYTE, sectorOffset(miniFatStart), sectorOffset(miniFatStart) + miniFatSectorCount * sectorSize);
216
+ file.fill(FREESECT_FILL_BYTE, sectorOffset(difatStart), sectorOffset(difatStart) + difatSectorCount * sectorSize);
217
+ file.fill(FREESECT_FILL_BYTE, HEADER_DIFAT_OFFSET, 512);
218
+ const setFat = (sector, value) => {
219
+ putU32(sectorOffset(Math.floor(sector / entriesPerFatSector)) + sector % entriesPerFatSector * 4, value);
220
+ };
221
+ const chainSectors = (start, count) => {
222
+ for (let i = 0; i < count; i++) setFat(start + i, i === count - 1 ? ENDOFCHAIN : start + i + 1);
223
+ };
224
+ for (let i = 0; i < fatSectorCount; i++) setFat(i, FATSECT);
225
+ for (let i = 0; i < difatSectorCount; i++) setFat(difatStart + i, DIFSECT);
226
+ chainSectors(directoryStart, directorySectorCount);
227
+ for (const { entry, bytes } of fatResident) chainSectors(entry.startSector, Math.ceil(bytes.length / sectorSize));
228
+ chainSectors(miniStreamStart, miniStreamSectorCount);
229
+ chainSectors(miniFatStart, miniFatSectorCount);
230
+ for (let i = 0; i < Math.min(fatSectorCount, HEADER_DIFAT_ENTRIES); i++) putU32(HEADER_DIFAT_OFFSET + i * 4, i);
231
+ for (let sector = 0; sector < difatSectorCount; sector++) {
232
+ const base = sectorOffset(difatStart + sector);
233
+ for (let i = 0; i < difatEntriesPerSector; i++) {
234
+ const fatIndex = HEADER_DIFAT_ENTRIES + sector * difatEntriesPerSector + i;
235
+ if (fatIndex < fatSectorCount) putU32(base + i * 4, fatIndex);
236
+ }
237
+ putU32(base + difatEntriesPerSector * 4, sector === difatSectorCount - 1 ? ENDOFCHAIN : difatStart + sector + 1);
238
+ }
239
+ const setMiniFat = (miniSector, value) => {
240
+ putU32(sectorOffset(miniFatStart + Math.floor(miniSector / entriesPerFatSector)) + miniSector % entriesPerFatSector * 4, value);
241
+ };
242
+ for (const { entry, bytes } of miniResident) {
243
+ const count = Math.ceil(bytes.length / MINI_SECTOR_SIZE);
244
+ for (let i = 0; i < count; i++) setMiniFat(entry.startSector + i, i === count - 1 ? ENDOFCHAIN : entry.startSector + i + 1);
245
+ }
246
+ for (const { entry, bytes } of fatResident) file.set(bytes, sectorOffset(entry.startSector));
247
+ for (const { entry, bytes } of miniResident) file.set(bytes, sectorOffset(miniStreamStart) + entry.startSector * MINI_SECTOR_SIZE);
248
+ const entryOffset = (id) => sectorOffset(directoryStart + Math.floor(id / entriesPerDirectorySector)) + id % entriesPerDirectorySector * DIRECTORY_ENTRY_SIZE;
249
+ for (const entry of plans) {
250
+ const base = entryOffset(entry.id);
251
+ const name = entry.node.name;
252
+ for (let i = 0; i < name.length; i++) putU16(base + i * 2, name.charCodeAt(i));
253
+ putU16(base + 64, (name.length + 1) * 2);
254
+ view.setUint8(base + 66, objectTypeOf(entry));
255
+ view.setUint8(base + 67, entry.colour);
256
+ putU32(base + 68, entry.left);
257
+ putU32(base + 72, entry.right);
258
+ putU32(base + 76, entry.child);
259
+ putU32(base + 116, entry.startSector);
260
+ putU32(base + 120, entry.size >>> 0);
261
+ putU32(base + 124, Math.floor(entry.size / 4294967296));
262
+ }
263
+ for (let id = plans.length; id < directorySectorCount * entriesPerDirectorySector; id++) {
264
+ const base = entryOffset(id);
265
+ putU32(base + 68, NOSTREAM);
266
+ putU32(base + 72, NOSTREAM);
267
+ putU32(base + 76, NOSTREAM);
268
+ }
269
+ file.set([
270
+ 208,
271
+ 207,
272
+ 17,
273
+ 224,
274
+ 161,
275
+ 177,
276
+ 26,
277
+ 225
278
+ ], 0);
279
+ putU16(24, 62);
280
+ putU16(26, majorVersion);
281
+ putU16(28, 65534);
282
+ putU16(30, sectorShift);
283
+ putU16(32, MINI_SECTOR_SHIFT);
284
+ putU32(40, majorVersion === 3 ? 0 : directorySectorCount);
285
+ putU32(44, fatSectorCount);
286
+ putU32(48, directoryStart);
287
+ putU32(56, MINI_STREAM_CUTOFF);
288
+ putU32(60, miniFatSectorCount === 0 ? ENDOFCHAIN : miniFatStart);
289
+ putU32(64, miniFatSectorCount);
290
+ putU32(68, difatSectorCount === 0 ? ENDOFCHAIN : difatStart);
291
+ putU32(72, difatSectorCount);
292
+ return file;
293
+ }
294
+ //#endregion
295
+ export { CompoundFileWriteError, writeCompoundFile };
package/dist/index.cjs CHANGED
@@ -2,20 +2,37 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_cfb_detect = require("./cfb/detect.cjs");
3
3
  const require_cfb_ole_package = require("./cfb/ole-package.cjs");
4
4
  const require_cfb_read = require("./cfb/read.cjs");
5
+ const require_cfb_write = require("./cfb/write.cjs");
6
+ const require_oleps_layout_metadata = require("./oleps/layout-metadata.cjs");
7
+ const require_oleps_read = require("./oleps/read.cjs");
8
+ const require_oleps_write = require("./oleps/write.cjs");
9
+ const require_oleps_summary_information = require("./oleps/summary-information.cjs");
5
10
  const require_zip_container = require("./zip/container.cjs");
6
11
  const require_zip_detect = require("./zip/detect.cjs");
7
12
  const require_zip_walk = require("./zip/walk.cjs");
8
13
  exports.ArchiveWalkLimitError = require_zip_walk.ArchiveWalkLimitError;
9
14
  exports.CompoundFileFormatError = require_cfb_read.CompoundFileFormatError;
15
+ exports.CompoundFileWriteError = require_cfb_write.CompoundFileWriteError;
16
+ exports.FMTID_SUMMARY_INFORMATION = require_oleps_summary_information.FMTID_SUMMARY_INFORMATION;
10
17
  exports.MAX_CFB_TOTAL_STREAM_BYTES = require_cfb_read.MAX_CFB_TOTAL_STREAM_BYTES;
11
18
  exports.MAX_WALK_DEPTH = require_zip_walk.MAX_WALK_DEPTH;
12
19
  exports.MAX_WALK_TOTAL_BYTES = require_zip_walk.MAX_WALK_TOTAL_BYTES;
13
20
  exports.OlePackageFormatError = require_cfb_ole_package.OlePackageFormatError;
21
+ exports.PropertySetFormatError = require_oleps_read.PropertySetFormatError;
22
+ exports.PropertySetWriteError = require_oleps_write.PropertySetWriteError;
14
23
  exports.detectArchiveFormat = require_zip_detect.detectArchiveFormat;
24
+ exports.hasSummaryInformationFields = require_oleps_layout_metadata.hasSummaryInformationFields;
15
25
  exports.isCompoundFile = require_cfb_detect.isCompoundFile;
16
26
  exports.isZipArchive = require_zip_detect.isZipArchive;
27
+ exports.layoutMetadataToSummaryInformation = require_oleps_layout_metadata.layoutMetadataToSummaryInformation;
17
28
  exports.readCompoundFile = require_cfb_read.readCompoundFile;
18
29
  exports.readOlePackage = require_cfb_ole_package.readOlePackage;
30
+ exports.readPropertySetStream = require_oleps_read.readPropertySetStream;
31
+ exports.readSummaryInformation = require_oleps_summary_information.readSummaryInformation;
32
+ exports.summaryInformationToLayoutMetadata = require_oleps_layout_metadata.summaryInformationToLayoutMetadata;
19
33
  exports.unzipPackage = require_zip_container.unzipPackage;
20
34
  exports.walkArchive = require_zip_walk.walkArchive;
35
+ exports.writeCompoundFile = require_cfb_write.writeCompoundFile;
36
+ exports.writePropertySetStream = require_oleps_write.writePropertySetStream;
37
+ exports.writeSummaryInformationStream = require_oleps_summary_information.writeSummaryInformationStream;
21
38
  exports.zipPackage = require_zip_container.zipPackage;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,13 @@
1
1
  import { isCompoundFile } from "./cfb/detect.cjs";
2
2
  import { OlePackage, OlePackageFormatError, readOlePackage } from "./cfb/ole-package.cjs";
3
3
  import { CompoundFileFormatError, CompoundFileStream, MAX_CFB_TOTAL_STREAM_BYTES, ReadCompoundFileOptions, readCompoundFile } from "./cfb/read.cjs";
4
+ import { CompoundFileWriteError, WriteCompoundFileOptions, writeCompoundFile } from "./cfb/write.cjs";
5
+ import { FMTID_SUMMARY_INFORMATION, SummaryInformationProperties, readSummaryInformation, writeSummaryInformationStream } from "./oleps/summary-information.cjs";
6
+ import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.cjs";
7
+ import { PropertySet, PropertyValue } from "./oleps/wire.cjs";
8
+ import { PropertySetFormatError, readPropertySetStream } from "./oleps/read.cjs";
9
+ import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.cjs";
4
10
  import { ZipEntry, unzipPackage, zipPackage } from "./zip/container.cjs";
5
11
  import { ArchiveFormat, detectArchiveFormat, isZipArchive } from "./zip/detect.cjs";
6
12
  import { ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, WalkArchiveOptions, walkArchive } from "./zip/walk.cjs";
7
- export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackage, OlePackageFormatError, ReadCompoundFileOptions, WalkArchiveOptions, ZipEntry, detectArchiveFormat, isCompoundFile, isZipArchive, readCompoundFile, readOlePackage, unzipPackage, walkArchive, zipPackage };
13
+ export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackage, OlePackageFormatError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writePropertySetStream, writeSummaryInformationStream, zipPackage };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  import { isCompoundFile } from "./cfb/detect.js";
2
2
  import { OlePackage, OlePackageFormatError, readOlePackage } from "./cfb/ole-package.js";
3
3
  import { CompoundFileFormatError, CompoundFileStream, MAX_CFB_TOTAL_STREAM_BYTES, ReadCompoundFileOptions, readCompoundFile } from "./cfb/read.js";
4
+ import { CompoundFileWriteError, WriteCompoundFileOptions, writeCompoundFile } from "./cfb/write.js";
5
+ import { FMTID_SUMMARY_INFORMATION, SummaryInformationProperties, readSummaryInformation, writeSummaryInformationStream } from "./oleps/summary-information.js";
6
+ import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.js";
7
+ import { PropertySet, PropertyValue } from "./oleps/wire.js";
8
+ import { PropertySetFormatError, readPropertySetStream } from "./oleps/read.js";
9
+ import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.js";
4
10
  import { ZipEntry, unzipPackage, zipPackage } from "./zip/container.js";
5
11
  import { ArchiveFormat, detectArchiveFormat, isZipArchive } from "./zip/detect.js";
6
12
  import { ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, WalkArchiveOptions, walkArchive } from "./zip/walk.js";
7
- export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackage, OlePackageFormatError, ReadCompoundFileOptions, WalkArchiveOptions, ZipEntry, detectArchiveFormat, isCompoundFile, isZipArchive, readCompoundFile, readOlePackage, unzipPackage, walkArchive, zipPackage };
13
+ export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackage, OlePackageFormatError, type PropertySet, PropertySetFormatError, PropertySetWriteError, type PropertyValue, ReadCompoundFileOptions, SummaryInformationProperties, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writePropertySetStream, writeSummaryInformationStream, zipPackage };
package/dist/index.js CHANGED
@@ -1,7 +1,12 @@
1
1
  import { isCompoundFile } from "./cfb/detect.js";
2
2
  import { OlePackageFormatError, readOlePackage } from "./cfb/ole-package.js";
3
3
  import { CompoundFileFormatError, MAX_CFB_TOTAL_STREAM_BYTES, readCompoundFile } from "./cfb/read.js";
4
+ import { CompoundFileWriteError, writeCompoundFile } from "./cfb/write.js";
5
+ import { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata } from "./oleps/layout-metadata.js";
6
+ import { PropertySetFormatError, readPropertySetStream } from "./oleps/read.js";
7
+ import { PropertySetWriteError, writePropertySetStream } from "./oleps/write.js";
8
+ import { FMTID_SUMMARY_INFORMATION, readSummaryInformation, writeSummaryInformationStream } from "./oleps/summary-information.js";
4
9
  import { unzipPackage, zipPackage } from "./zip/container.js";
5
10
  import { detectArchiveFormat, isZipArchive } from "./zip/detect.js";
6
11
  import { ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, walkArchive } from "./zip/walk.js";
7
- export { ArchiveWalkLimitError, CompoundFileFormatError, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackageFormatError, detectArchiveFormat, isCompoundFile, isZipArchive, readCompoundFile, readOlePackage, unzipPackage, walkArchive, zipPackage };
12
+ export { ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileWriteError, FMTID_SUMMARY_INFORMATION, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackageFormatError, PropertySetFormatError, PropertySetWriteError, detectArchiveFormat, hasSummaryInformationFields, isCompoundFile, isZipArchive, layoutMetadataToSummaryInformation, readCompoundFile, readOlePackage, readPropertySetStream, readSummaryInformation, summaryInformationToLayoutMetadata, unzipPackage, walkArchive, writeCompoundFile, writePropertySetStream, writeSummaryInformationStream, zipPackage };
@@ -0,0 +1,30 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/oleps/layout-metadata.ts
3
+ function summaryInformationToLayoutMetadata(info) {
4
+ return {
5
+ title: info.title,
6
+ subject: info.subject,
7
+ author: info.author,
8
+ keywords: info.keywords === void 0 ? void 0 : [...info.keywords],
9
+ createdIso: info.createdIso,
10
+ modifiedIso: info.lastSavedIso
11
+ };
12
+ }
13
+ function layoutMetadataToSummaryInformation(metadata) {
14
+ return {
15
+ title: metadata.title,
16
+ subject: metadata.subject,
17
+ author: metadata.author,
18
+ keywords: metadata.keywords,
19
+ createdIso: metadata.createdIso,
20
+ lastSavedIso: metadata.modifiedIso
21
+ };
22
+ }
23
+ /** Whether a LayoutMetadata carries anything SummaryInformation can actually represent -- `creator`/`producer`/`language` alone should not force a stream carrying nothing but the CodePage property into existence, since a reader would see that back as `{}` regardless (see each caller's own write-side entry point). */
24
+ function hasSummaryInformationFields(metadata) {
25
+ return metadata.title !== void 0 || metadata.subject !== void 0 || metadata.author !== void 0 || metadata.keywords !== void 0 && metadata.keywords.length > 0 || metadata.createdIso !== void 0 || metadata.modifiedIso !== void 0;
26
+ }
27
+ //#endregion
28
+ exports.hasSummaryInformationFields = hasSummaryInformationFields;
29
+ exports.layoutMetadataToSummaryInformation = layoutMetadataToSummaryInformation;
30
+ exports.summaryInformationToLayoutMetadata = summaryInformationToLayoutMetadata;
@@ -0,0 +1,9 @@
1
+ import { SummaryInformationProperties } from "./summary-information.cjs";
2
+ import { LayoutMetadata } from "document-schema.js";
3
+ //#region src/oleps/layout-metadata.d.ts
4
+ declare function summaryInformationToLayoutMetadata(info: SummaryInformationProperties): LayoutMetadata;
5
+ declare function layoutMetadataToSummaryInformation(metadata: LayoutMetadata): SummaryInformationProperties;
6
+ /** Whether a LayoutMetadata carries anything SummaryInformation can actually represent -- `creator`/`producer`/`language` alone should not force a stream carrying nothing but the CodePage property into existence, since a reader would see that back as `{}` regardless (see each caller's own write-side entry point). */
7
+ declare function hasSummaryInformationFields(metadata: LayoutMetadata): boolean;
8
+ //#endregion
9
+ export { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata };
@@ -0,0 +1,9 @@
1
+ import { SummaryInformationProperties } from "./summary-information.js";
2
+ import { LayoutMetadata } from "document-schema.js";
3
+ //#region src/oleps/layout-metadata.d.ts
4
+ declare function summaryInformationToLayoutMetadata(info: SummaryInformationProperties): LayoutMetadata;
5
+ declare function layoutMetadataToSummaryInformation(metadata: LayoutMetadata): SummaryInformationProperties;
6
+ /** Whether a LayoutMetadata carries anything SummaryInformation can actually represent -- `creator`/`producer`/`language` alone should not force a stream carrying nothing but the CodePage property into existence, since a reader would see that back as `{}` regardless (see each caller's own write-side entry point). */
7
+ declare function hasSummaryInformationFields(metadata: LayoutMetadata): boolean;
8
+ //#endregion
9
+ export { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata };
@@ -0,0 +1,27 @@
1
+ //#region src/oleps/layout-metadata.ts
2
+ function summaryInformationToLayoutMetadata(info) {
3
+ return {
4
+ title: info.title,
5
+ subject: info.subject,
6
+ author: info.author,
7
+ keywords: info.keywords === void 0 ? void 0 : [...info.keywords],
8
+ createdIso: info.createdIso,
9
+ modifiedIso: info.lastSavedIso
10
+ };
11
+ }
12
+ function layoutMetadataToSummaryInformation(metadata) {
13
+ return {
14
+ title: metadata.title,
15
+ subject: metadata.subject,
16
+ author: metadata.author,
17
+ keywords: metadata.keywords,
18
+ createdIso: metadata.createdIso,
19
+ lastSavedIso: metadata.modifiedIso
20
+ };
21
+ }
22
+ /** Whether a LayoutMetadata carries anything SummaryInformation can actually represent -- `creator`/`producer`/`language` alone should not force a stream carrying nothing but the CodePage property into existence, since a reader would see that back as `{}` regardless (see each caller's own write-side entry point). */
23
+ function hasSummaryInformationFields(metadata) {
24
+ return metadata.title !== void 0 || metadata.subject !== void 0 || metadata.author !== void 0 || metadata.keywords !== void 0 && metadata.keywords.length > 0 || metadata.createdIso !== void 0 || metadata.modifiedIso !== void 0;
25
+ }
26
+ //#endregion
27
+ export { hasSummaryInformationFields, layoutMetadataToSummaryInformation, summaryInformationToLayoutMetadata };
@@ -0,0 +1,132 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_oleps_wire = require("./wire.cjs");
3
+ //#region src/oleps/read.ts
4
+ var PropertySetFormatError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "PropertySetFormatError";
8
+ }
9
+ };
10
+ function requireBytes(byteLength, offset, length, what) {
11
+ if (offset < 0 || length < 0 || offset + length > byteLength) throw new PropertySetFormatError(`property set stream ends before ${what} (needs ${length} bytes at offset ${offset}, stream is ${byteLength} bytes)`);
12
+ }
13
+ const ANSI_DECODER = new TextDecoder("windows-1252");
14
+ const UTF16_DECODER = new TextDecoder("utf-16le");
15
+ function decodeAnsi(bytes, codepage) {
16
+ if (codepage !== 1252) return;
17
+ return ANSI_DECODER.decode(bytes);
18
+ }
19
+ function truncateAtNull(value) {
20
+ const index = value.indexOf("\0");
21
+ return index === -1 ? value : value.slice(0, index);
22
+ }
23
+ function readCodePageString(bytes, view, offset, codepage) {
24
+ requireBytes(bytes.length, offset, 4, "a CodePageString's Size field");
25
+ const size = view.getUint32(offset, true);
26
+ requireBytes(bytes.length, offset + 4, size, "a CodePageString's Characters field");
27
+ const raw = bytes.subarray(offset + 4, offset + 4 + size);
28
+ if (codepage === 1200) return truncateAtNull(UTF16_DECODER.decode(raw));
29
+ const decoded = decodeAnsi(raw, codepage);
30
+ return decoded === void 0 ? void 0 : truncateAtNull(decoded);
31
+ }
32
+ function readUnicodeString(bytes, view, offset) {
33
+ requireBytes(bytes.length, offset, 4, "a UnicodeString's Length field");
34
+ const byteLength = view.getUint32(offset, true) * 2;
35
+ requireBytes(bytes.length, offset + 4, byteLength, "a UnicodeString's Characters field");
36
+ const raw = bytes.subarray(offset + 4, offset + 4 + byteLength);
37
+ return truncateAtNull(UTF16_DECODER.decode(raw));
38
+ }
39
+ function readPropertySetStream(bytes) {
40
+ requireBytes(bytes.length, 0, 48, "the PropertySetStream header");
41
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
42
+ const byteOrder = view.getUint16(0, true);
43
+ if (byteOrder !== 65534) throw new PropertySetFormatError(`property set stream's ByteOrder field is 0x${byteOrder.toString(16)}, not the mandated 0xFFFE`);
44
+ const numPropertySets = view.getUint32(24, true);
45
+ if (numPropertySets !== 1) throw new PropertySetFormatError(`property set stream declares ${numPropertySets} property sets; this reader only handles the single-property-set form every "\\x05SummaryInformation" stream uses (the two-property-set DocumentSummaryInformation/UserDefinedProperties spelling is out of scope, see the package README)`);
46
+ const formatId = require_oleps_wire.readGuid(view, 28);
47
+ const offset0 = view.getUint32(44, true);
48
+ requireBytes(bytes.length, offset0, 8, "the PropertySet packet header");
49
+ const size = view.getUint32(offset0, true);
50
+ requireBytes(bytes.length, offset0, size, "the PropertySet packet's own declared Size");
51
+ const numProperties = view.getUint32(offset0 + 4, true);
52
+ const tableStart = offset0 + 8;
53
+ requireBytes(bytes.length, tableStart, numProperties * 8, "the PropertyIdentifierAndOffset dictionary");
54
+ const entries = [];
55
+ for (let i = 0; i < numProperties; i++) {
56
+ const entryOffset = tableStart + i * 8;
57
+ const pid = view.getUint32(entryOffset, true);
58
+ if (pid === 0) throw new PropertySetFormatError("property set carries a Dictionary property (PID 0), which names string-keyed properties this reader does not support -- no \"\\x05SummaryInformation\" stream should carry one");
59
+ entries.push({
60
+ pid,
61
+ relativeOffset: view.getUint32(entryOffset + 4, true)
62
+ });
63
+ }
64
+ let codepage = require_oleps_wire.WINDOWS_1252_CODEPAGE;
65
+ for (const entry of entries) {
66
+ if (entry.pid !== 1) continue;
67
+ const abs = offset0 + entry.relativeOffset;
68
+ requireBytes(bytes.length, abs, 8, "the CodePage property's TypedPropertyValue");
69
+ const type = view.getUint16(abs, true);
70
+ if (type !== 2) throw new PropertySetFormatError(`CodePage property (PID 1) has type 0x${type.toString(16)}, not VT_I2 as [MS-OLEPS] requires`);
71
+ const raw = view.getInt16(abs + 4, true);
72
+ codepage = raw < 0 ? raw + 65536 : raw;
73
+ }
74
+ const properties = /* @__PURE__ */ new Map();
75
+ for (const entry of entries) {
76
+ const abs = offset0 + entry.relativeOffset;
77
+ requireBytes(bytes.length, abs, 4, "a property's TypedPropertyValue header");
78
+ const type = view.getUint16(abs, true);
79
+ const padding = view.getUint16(abs + 2, true);
80
+ if (padding !== 0) throw new PropertySetFormatError(`property ${entry.pid}'s TypedPropertyValue padding is 0x${padding.toString(16)}, not zero as [MS-OLEPS] requires`);
81
+ const valueOffset = abs + 4;
82
+ switch (type) {
83
+ case 2:
84
+ requireBytes(bytes.length, valueOffset, 4, `property ${entry.pid}'s VT_I2 value`);
85
+ properties.set(entry.pid, {
86
+ type: "VT_I2",
87
+ value: view.getInt16(valueOffset, true)
88
+ });
89
+ break;
90
+ case 3:
91
+ requireBytes(bytes.length, valueOffset, 4, `property ${entry.pid}'s VT_I4 value`);
92
+ properties.set(entry.pid, {
93
+ type: "VT_I4",
94
+ value: view.getInt32(valueOffset, true)
95
+ });
96
+ break;
97
+ case 30: {
98
+ const value = readCodePageString(bytes, view, valueOffset, codepage);
99
+ if (value !== void 0) properties.set(entry.pid, {
100
+ type: "VT_LPSTR",
101
+ value
102
+ });
103
+ break;
104
+ }
105
+ case 31: {
106
+ const value = readUnicodeString(bytes, view, valueOffset);
107
+ properties.set(entry.pid, {
108
+ type: "VT_LPWSTR",
109
+ value
110
+ });
111
+ break;
112
+ }
113
+ case 64: {
114
+ requireBytes(bytes.length, valueOffset, 8, `property ${entry.pid}'s VT_FILETIME value`);
115
+ const low = view.getUint32(valueOffset, true);
116
+ const high = view.getUint32(valueOffset + 4, true);
117
+ properties.set(entry.pid, {
118
+ type: "VT_FILETIME",
119
+ value: require_oleps_wire.filetimeToDate(low, high)
120
+ });
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ return {
126
+ formatId,
127
+ properties
128
+ };
129
+ }
130
+ //#endregion
131
+ exports.PropertySetFormatError = PropertySetFormatError;
132
+ exports.readPropertySetStream = readPropertySetStream;
@@ -0,0 +1,8 @@
1
+ import { PropertySet } from "./wire.cjs";
2
+ //#region src/oleps/read.d.ts
3
+ declare class PropertySetFormatError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ declare function readPropertySetStream(bytes: Uint8Array<ArrayBuffer>): PropertySet;
7
+ //#endregion
8
+ export { PropertySetFormatError, readPropertySetStream };
@@ -0,0 +1,8 @@
1
+ import { PropertySet } from "./wire.js";
2
+ //#region src/oleps/read.d.ts
3
+ declare class PropertySetFormatError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ declare function readPropertySetStream(bytes: Uint8Array<ArrayBuffer>): PropertySet;
7
+ //#endregion
8
+ export { PropertySetFormatError, readPropertySetStream };