archive-codec 1.3.0 → 1.4.1
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/README.md +47 -12
- package/dist/index.cjs +14 -0
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -1
- package/dist/oleps/layout-metadata.cjs +30 -0
- package/dist/oleps/layout-metadata.d.cts +9 -0
- package/dist/oleps/layout-metadata.d.ts +9 -0
- package/dist/oleps/layout-metadata.js +27 -0
- package/dist/oleps/read.cjs +132 -0
- package/dist/oleps/read.d.cts +8 -0
- package/dist/oleps/read.d.ts +8 -0
- package/dist/oleps/read.js +130 -0
- package/dist/oleps/summary-information.cjs +94 -0
- package/dist/oleps/summary-information.d.cts +16 -0
- package/dist/oleps/summary-information.d.ts +16 -0
- package/dist/oleps/summary-information.js +91 -0
- package/dist/oleps/wire.cjs +70 -0
- package/dist/oleps/wire.d.cts +45 -0
- package/dist/oleps/wire.d.ts +45 -0
- package/dist/oleps/wire.js +51 -0
- package/dist/oleps/write.cjs +103 -0
- package/dist/oleps/write.d.cts +8 -0
- package/dist/oleps/write.d.ts +8 -0
- package/dist/oleps/write.js +101 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ExaDev/documents.js/tree/main/packages/archive-codec) [](https://www.npmjs.com/package/archive-codec) [](https://www.npmjs.com/package/archive-codec) [](https://github.com/ExaDev/documents.js/actions)
|
|
4
4
|
|
|
5
|
-
> ZIP-in-ZIP recursive walking under depth and cumulative decompressed-size guards,
|
|
5
|
+
> ZIP-in-ZIP recursive walking under depth and cumulative decompressed-size guards, classic OLE compound-file ([MS-CFB]) reading and writing, and [MS-OLEPS] Property Set Stream reading and writing — zero document-format knowledge, the archive and container utility package for the [documents.js family](../../README.md). Worker-isomorphic: the same code runs under Node and inside a Cloudflare Workers isolate.
|
|
6
6
|
|
|
7
7
|
Created for [documents.js#564](https://github.com/ExaDev/documents.js/issues/564): nothing in the ecosystem recursed into a nested archive. Most concretely, OOXML's embedded-object model — a docx/pptx carrying a genuinely separate ZIP blob at `word/embeddings/oleObject1.xlsx` — had no safe handling anywhere, and no package guarded against recursive-archive inputs at all (`byte-codec`'s 512 MiB per-stream inflate cap does not compose across recursion). A new sibling was chosen over extending `byte-codec` (whose charter is byte/image primitives, zero container-format knowledge) or doing it inline in `documents.js` (which would repeat the duplication `byte-codec`'s own extraction was meant to avoid). Its first family consumer is `ooxml.js`'s OLE embedded-object recovery — [documents.js#733](https://github.com/ExaDev/documents.js/issues/733) (pptx, `p:oleObj`) and [documents.js#734](https://github.com/ExaDev/documents.js/issues/734) (docx, `o:OLEObject`): an OLE payload part's bytes are checked through `isZipArchive` and, when they are a ZIP, decoded as a nested OOXML package behind this package's guarded walk — the bounded inflate that populates `document-schema.js`'s `ContentEmbeddedObject`/`ContentEmbeddedObjectBlock` (the same vocabulary odf.js embeds formula sub-documents through) with a genuinely recovered sub-document.
|
|
8
8
|
|
|
@@ -10,7 +10,9 @@ Created for [documents.js#564](https://github.com/ExaDev/documents.js/issues/564
|
|
|
10
10
|
|
|
11
11
|
[documents.js#815](https://github.com/ExaDev/documents.js/issues/815), [#816](https://github.com/ExaDev/documents.js/issues/816), and [#817](https://github.com/ExaDev/documents.js/issues/817) then needed the other direction. `xls-codec`, `doc-codec`, `ppt-codec`, and `wpd-codec` each read a legacy Office binary format out of an [MS-CFB] container, and none of them can write one back, because there was no container to put their streams into: a `.xls` writer producing a `Workbook` stream, or a `.doc` writer producing `WordDocument` and `1Table`, needs a conformant compound file to hold them. That container is structural knowledge exactly as the reader's is, so `writeCompoundFile` is the mirror of `readCompoundFile` here rather than four hand-rolled emitters in four codecs.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
[documents.js#815](https://github.com/ExaDev/documents.js/issues/815), [#816](https://github.com/ExaDev/documents.js/issues/816), and [#817](https://github.com/ExaDev/documents.js/issues/817) also each named the same remaining gap: `doc-codec`, `xls-codec`, and `ppt-codec` all hard-coded document metadata (title, author, dates) to an empty object, because that metadata lives in a genuinely different structure from the one each format's own reader already parses -- a [MS-OLEPS] Property Set Stream, conventionally stored as a "\x05SummaryInformation" stream beside `WordDocument`/`Workbook`/`PowerPoint Document` in the identical [MS-CFB] container all three already read through this package. `oleps/read` and `oleps/write` are the generic property-set codec (the stream header, the PropertySet packet's dictionary, and VT_I2/VT_I4/VT_LPSTR/VT_LPWSTR/VT_FILETIME typed values), and `oleps/summary-information` is the SummaryInformation-specific mapping on top of it -- the same two-layer split `cfb/read.ts` and `cfb/ole-package.ts` already establish for the OLE Package stream, container structure below, one named stream's own field layout above.
|
|
14
|
+
|
|
15
|
+
Scope: **ZIP containers** (read and write over [`fflate`](https://github.com/101arrowz/fflate), recursive walking of ZIP-in-ZIP entries), **classic OLE compound files** (bounded [MS-CFB] reading and conformant [MS-CFB] writing, plus the OLE Package stream unwrapping), and **[MS-OLEPS] Property Set Streams** (generic read/write of a single-property-set stream, plus SummaryInformation's own title/subject/author/keywords/comments/created/last-saved/last-printed fields). **tar and gzip, DocumentSummaryInformation's extended and user-defined property sets, and writing VT_LPSTR (ANSI-codepage) string properties are explicitly out of scope.**
|
|
14
16
|
|
|
15
17
|
## Getting started
|
|
16
18
|
|
|
@@ -40,15 +42,19 @@ import { walkArchive } from "archive-codec/zip/walk";
|
|
|
40
42
|
|
|
41
43
|
The smoke suite (`test/smoke.test.mjs`) is the guard on that advertisement: it loads each module below from the built `dist/` in both module systems, so a build config that stops serving an advertised subpath fails the suite — neither publint nor `attw` catches a wildcard whose targets are missing.
|
|
42
44
|
|
|
43
|
-
| Module
|
|
44
|
-
|
|
|
45
|
-
| `zip/container`
|
|
46
|
-
| `zip/detect`
|
|
47
|
-
| `zip/walk`
|
|
48
|
-
| `cfb/detect`
|
|
49
|
-
| `cfb/read`
|
|
50
|
-
| `cfb/write`
|
|
51
|
-
| `cfb/ole-package`
|
|
45
|
+
| Module | Exports |
|
|
46
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| `zip/container` | `zipPackage` (ordered-entries ZIP write with stored-uncompressed support), `unzipPackage`, `ZipEntry` |
|
|
48
|
+
| `zip/detect` | `detectArchiveFormat` (`'zip' \| 'cfb' \| 'unknown'`), `isZipArchive`, `ArchiveFormat` |
|
|
49
|
+
| `zip/walk` | `walkArchive` (recursive ZIP-in-ZIP walking), `ArchiveWalkEntry`, `ArchiveWalkLimitError`, `MAX_WALK_DEPTH`, `MAX_WALK_TOTAL_BYTES`, `WalkArchiveOptions` |
|
|
50
|
+
| `cfb/detect` | `isCompoundFile` (the `D0 CF 11 E0 …` magic-byte check) |
|
|
51
|
+
| `cfb/read` | `readCompoundFile` (bounded [MS-CFB] stream extraction), `CompoundFileStream`, `CompoundFileFormatError`, `MAX_CFB_TOTAL_STREAM_BYTES`, `ReadCompoundFileOptions` |
|
|
52
|
+
| `cfb/write` | `writeCompoundFile` ([MS-CFB] container generation), `CompoundFileWriteError`, `WriteCompoundFileOptions` — takes the `CompoundFileStream` array `cfb/read` returns |
|
|
53
|
+
| `cfb/ole-package` | `readOlePackage` (OLE Package stream unwrapping), `OlePackage`, `OlePackageFormatError` |
|
|
54
|
+
| `oleps/read` | `readPropertySetStream` (generic [MS-OLEPS] property-set decoding), `PropertySetFormatError` |
|
|
55
|
+
| `oleps/write` | `writePropertySetStream` (generic [MS-OLEPS] property-set encoding), `PropertySetWriteError` — takes the `PropertySet` shape `oleps/read` returns |
|
|
56
|
+
| `oleps/summary-information` | `readSummaryInformation`, `writeSummaryInformationStream`, `SummaryInformationProperties`, `FMTID_SUMMARY_INFORMATION` |
|
|
57
|
+
| `oleps/layout-metadata` | `summaryInformationToLayoutMetadata`, `layoutMetadataToSummaryInformation`, `hasSummaryInformationFields` — the `SummaryInformationProperties` <-> `document-schema.js`'s `LayoutMetadata` mapping, shared by `doc-codec`/`xls-codec`/`ppt-codec` (see [Property sets](#property-sets)) |
|
|
52
58
|
|
|
53
59
|
### Recursive walking
|
|
54
60
|
|
|
@@ -113,6 +119,35 @@ Two details are deliberate rather than incidental. The directory's sibling trees
|
|
|
113
119
|
|
|
114
120
|
Correctness is checked against independent parsers, not only against this package's own reader: the written files are accepted by [`olefile`](https://github.com/decalage2/olefile) in its strict `DEFECT_INCORRECT` mode and by 7-Zip's Compound handler, both of which return byte-identical stream content, and a real LibreOffice-authored `.doc` read through `readCompoundFile` and re-emitted through `writeCompoundFile` still opens in LibreOffice Writer.
|
|
115
121
|
|
|
122
|
+
### Property sets
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import {
|
|
126
|
+
readCompoundFile,
|
|
127
|
+
readSummaryInformation,
|
|
128
|
+
writeSummaryInformationStream,
|
|
129
|
+
} from "archive-codec";
|
|
130
|
+
|
|
131
|
+
const stream = readCompoundFile(docBytes).find(
|
|
132
|
+
(s) => s.path === "\x05SummaryInformation",
|
|
133
|
+
);
|
|
134
|
+
if (stream !== undefined) {
|
|
135
|
+
const metadata = readSummaryInformation(stream.bytes);
|
|
136
|
+
metadata.title; // string | undefined
|
|
137
|
+
metadata.createdIso; // string | undefined, ISO-8601
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// The mirror image: builds a "\x05SummaryInformation" stream's bytes from the
|
|
141
|
+
// same shape, to hand to writeCompoundFile alongside the format's own streams.
|
|
142
|
+
const summaryStream = writeSummaryInformationStream({ title: "Q3 report" });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`readSummaryInformation`/`writeSummaryInformationStream` cover the seven SummaryInformation fields a caller actually needs (title, subject, author, keywords, comments, and the created/last-saved/last-printed FILETIME timestamps, as ISO-8601 strings); everything else the property set can carry (template, last author, revision number, application name, edit time, page/word/character counts, document security) is read into the stream but not projected into `SummaryInformationProperties`, and the separate `"\x05DocumentSummaryInformation"` stream (company, manager, and custom user-defined properties) is not read or written at all. `readPropertySetStream`/`writePropertySetStream` are the generic layer beneath it — a `PropertySet`'s `formatId` and its `properties` map, keyed by `PropertyIdentifier`, valued by a `{ type, value }` pair over `VT_I2`/`VT_I4`/`VT_LPSTR`/`VT_LPWSTR`/`VT_FILETIME` — for a caller working with a different, non-SummaryInformation property set built on the identical [MS-OLEPS] wire format. The writer only emits `VT_LPWSTR` (Unicode) strings, never `VT_LPSTR`: a `CodePageString`'s ANSI encoding depends on the property set's own CodePage property, and writing an arbitrary codepage's bytes would need a full codepage table this package does not carry, so `VT_LPWSTR`'s codepage-independent UTF-16LE sidesteps the question entirely. The reader still decodes `VT_LPSTR` on the way in — `CP_WINUNICODE` (1200) and windows-1252 (1252, the value the [MS-OLEPS] SummaryInformation worked example itself declares, and the same ANSI convention `cfb/ole-package.ts` already uses) — since a real Office-authored file almost always writes ANSI strings, not Unicode ones.
|
|
146
|
+
|
|
147
|
+
A property whose type this reader does not decode — an unsupported `PropertyType` (e.g. `VT_CF`, the clipboard-format type `PIDSI_THUMBNAIL` uses), or a `VT_LPSTR` under a `CodePage` other than `CP_WINUNICODE`/windows-1252 — is skipped rather than aborting the whole read: a real SummaryInformation stream routinely carries a thumbnail or a non-Western codepage, and one undecodable property must not fail a read that also carries properties this reader can decode. Genuine structural nonconformance (a bad `ByteOrder`, a truncated stream, a `Dictionary` property, non-zero `TypedPropertyValue` padding) still throws `PropertySetFormatError`.
|
|
148
|
+
|
|
149
|
+
`oleps/layout-metadata`'s `summaryInformationToLayoutMetadata`/`layoutMetadataToSummaryInformation`/`hasSummaryInformationFields` map `SummaryInformationProperties` to and from `document-schema.js`'s own `LayoutMetadata` — the shared metadata shape every codec's `ContentDocument` carries, format-agnostic rather than specific to any one legacy binary format. `doc-codec`, `xls-codec`, and `ppt-codec` each import these directly rather than maintaining their own copy, wrapping `layoutMetadataToSummaryInformation` with their own `createdIso`/`modifiedIso` date validation so a malformed date is reported through that package's own error vocabulary rather than an opaque `RangeError` out of the FILETIME conversion.
|
|
150
|
+
|
|
116
151
|
### ZIP container
|
|
117
152
|
|
|
118
153
|
`zipPackage` takes an _ordered_ array of `[path, entry]` tuples, not a `Record`, so the caller controls the exact emission order deterministically (the property formats with a fixed-offset first entry — ODF's `mimetype` — depend on), and any entry can be written stored-uncompressed via `stored: true`. `unzipPackage` is the read side; the returned `Record` makes no ordering promise and collapses duplicate paths.
|
|
@@ -121,7 +156,7 @@ Correctness is checked against independent parsers, not only against this packag
|
|
|
121
156
|
|
|
122
157
|
- Worker-isomorphic (see the [family-wide convention](../../README.md#conventions)): runtime `src/` must not import `node:*`, a bare Node builtin, or use the `Buffer` global — enforced by a `no-restricted-imports`/`no-restricted-globals` ESLint rule and exercised in CI by running the test suite inside an actual `workerd` isolate (`pnpm test:workers`). Test files under `src/**/*.test.ts` and `src/test-support/` are exempt and may use Node APIs for fixtures.
|
|
123
158
|
- Only `src/index.ts` may be named `index.*` — a custom ESLint rule (`local/no-non-barrel-index`) rejects any other module using an `index` basename, since that would be a hidden entry point the `exports` map in `package.json` doesn't advertise.
|
|
124
|
-
- Zero document-format knowledge: this package knows bytes and container structure — ZIP entries, compound-file sectors and directory entries, the OLE packaging wrapper — never that any entry or stream is a document. It depends only on `fflate` — not on `byte-codec`, `ooxml.js`, or `odf.js` (whose ZIP wrappers it deliberately mirrors rather than imports, keeping their branding and release cadences decoupled).
|
|
159
|
+
- Zero document-format knowledge: this package knows bytes and container structure — ZIP entries, compound-file sectors and directory entries, the OLE packaging wrapper, [MS-OLEPS] property identifiers and typed values — never that any entry or stream is a document, or that PID 2 means a title. It depends only on `fflate` — not on `byte-codec`, `ooxml.js`, or `odf.js` (whose ZIP wrappers it deliberately mirrors rather than imports, keeping their branding and release cadences decoupled).
|
|
125
160
|
|
|
126
161
|
## Install
|
|
127
162
|
|
package/dist/index.cjs
CHANGED
|
@@ -3,22 +3,36 @@ 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
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");
|
|
6
10
|
const require_zip_container = require("./zip/container.cjs");
|
|
7
11
|
const require_zip_detect = require("./zip/detect.cjs");
|
|
8
12
|
const require_zip_walk = require("./zip/walk.cjs");
|
|
9
13
|
exports.ArchiveWalkLimitError = require_zip_walk.ArchiveWalkLimitError;
|
|
10
14
|
exports.CompoundFileFormatError = require_cfb_read.CompoundFileFormatError;
|
|
11
15
|
exports.CompoundFileWriteError = require_cfb_write.CompoundFileWriteError;
|
|
16
|
+
exports.FMTID_SUMMARY_INFORMATION = require_oleps_summary_information.FMTID_SUMMARY_INFORMATION;
|
|
12
17
|
exports.MAX_CFB_TOTAL_STREAM_BYTES = require_cfb_read.MAX_CFB_TOTAL_STREAM_BYTES;
|
|
13
18
|
exports.MAX_WALK_DEPTH = require_zip_walk.MAX_WALK_DEPTH;
|
|
14
19
|
exports.MAX_WALK_TOTAL_BYTES = require_zip_walk.MAX_WALK_TOTAL_BYTES;
|
|
15
20
|
exports.OlePackageFormatError = require_cfb_ole_package.OlePackageFormatError;
|
|
21
|
+
exports.PropertySetFormatError = require_oleps_read.PropertySetFormatError;
|
|
22
|
+
exports.PropertySetWriteError = require_oleps_write.PropertySetWriteError;
|
|
16
23
|
exports.detectArchiveFormat = require_zip_detect.detectArchiveFormat;
|
|
24
|
+
exports.hasSummaryInformationFields = require_oleps_layout_metadata.hasSummaryInformationFields;
|
|
17
25
|
exports.isCompoundFile = require_cfb_detect.isCompoundFile;
|
|
18
26
|
exports.isZipArchive = require_zip_detect.isZipArchive;
|
|
27
|
+
exports.layoutMetadataToSummaryInformation = require_oleps_layout_metadata.layoutMetadataToSummaryInformation;
|
|
19
28
|
exports.readCompoundFile = require_cfb_read.readCompoundFile;
|
|
20
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;
|
|
21
33
|
exports.unzipPackage = require_zip_container.unzipPackage;
|
|
22
34
|
exports.walkArchive = require_zip_walk.walkArchive;
|
|
23
35
|
exports.writeCompoundFile = require_cfb_write.writeCompoundFile;
|
|
36
|
+
exports.writePropertySetStream = require_oleps_write.writePropertySetStream;
|
|
37
|
+
exports.writeSummaryInformationStream = require_oleps_summary_information.writeSummaryInformationStream;
|
|
24
38
|
exports.zipPackage = require_zip_container.zipPackage;
|
package/dist/index.d.cts
CHANGED
|
@@ -2,7 +2,12 @@ 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
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";
|
|
5
10
|
import { ZipEntry, unzipPackage, zipPackage } from "./zip/container.cjs";
|
|
6
11
|
import { ArchiveFormat, detectArchiveFormat, isZipArchive } from "./zip/detect.cjs";
|
|
7
12
|
import { ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, WalkArchiveOptions, walkArchive } from "./zip/walk.cjs";
|
|
8
|
-
export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, CompoundFileWriteError, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackage, OlePackageFormatError, ReadCompoundFileOptions, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, detectArchiveFormat, isCompoundFile, isZipArchive, readCompoundFile, readOlePackage, unzipPackage, walkArchive, writeCompoundFile, 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
|
@@ -2,7 +2,12 @@ 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
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";
|
|
5
10
|
import { ZipEntry, unzipPackage, zipPackage } from "./zip/container.js";
|
|
6
11
|
import { ArchiveFormat, detectArchiveFormat, isZipArchive } from "./zip/detect.js";
|
|
7
12
|
import { ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, WalkArchiveOptions, walkArchive } from "./zip/walk.js";
|
|
8
|
-
export { ArchiveFormat, ArchiveWalkEntry, ArchiveWalkLimit, ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileStream, CompoundFileWriteError, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackage, OlePackageFormatError, ReadCompoundFileOptions, WalkArchiveOptions, WriteCompoundFileOptions, ZipEntry, detectArchiveFormat, isCompoundFile, isZipArchive, readCompoundFile, readOlePackage, unzipPackage, walkArchive, writeCompoundFile, 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
|
@@ -2,7 +2,11 @@ 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
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";
|
|
5
9
|
import { unzipPackage, zipPackage } from "./zip/container.js";
|
|
6
10
|
import { detectArchiveFormat, isZipArchive } from "./zip/detect.js";
|
|
7
11
|
import { ArchiveWalkLimitError, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, walkArchive } from "./zip/walk.js";
|
|
8
|
-
export { ArchiveWalkLimitError, CompoundFileFormatError, CompoundFileWriteError, MAX_CFB_TOTAL_STREAM_BYTES, MAX_WALK_DEPTH, MAX_WALK_TOTAL_BYTES, OlePackageFormatError, detectArchiveFormat, isCompoundFile, isZipArchive, readCompoundFile, readOlePackage, unzipPackage, walkArchive, writeCompoundFile, 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 };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { WINDOWS_1252_CODEPAGE, filetimeToDate, readGuid } from "./wire.js";
|
|
2
|
+
//#region src/oleps/read.ts
|
|
3
|
+
var PropertySetFormatError = class extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "PropertySetFormatError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
function requireBytes(byteLength, offset, length, what) {
|
|
10
|
+
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)`);
|
|
11
|
+
}
|
|
12
|
+
const ANSI_DECODER = new TextDecoder("windows-1252");
|
|
13
|
+
const UTF16_DECODER = new TextDecoder("utf-16le");
|
|
14
|
+
function decodeAnsi(bytes, codepage) {
|
|
15
|
+
if (codepage !== 1252) return;
|
|
16
|
+
return ANSI_DECODER.decode(bytes);
|
|
17
|
+
}
|
|
18
|
+
function truncateAtNull(value) {
|
|
19
|
+
const index = value.indexOf("\0");
|
|
20
|
+
return index === -1 ? value : value.slice(0, index);
|
|
21
|
+
}
|
|
22
|
+
function readCodePageString(bytes, view, offset, codepage) {
|
|
23
|
+
requireBytes(bytes.length, offset, 4, "a CodePageString's Size field");
|
|
24
|
+
const size = view.getUint32(offset, true);
|
|
25
|
+
requireBytes(bytes.length, offset + 4, size, "a CodePageString's Characters field");
|
|
26
|
+
const raw = bytes.subarray(offset + 4, offset + 4 + size);
|
|
27
|
+
if (codepage === 1200) return truncateAtNull(UTF16_DECODER.decode(raw));
|
|
28
|
+
const decoded = decodeAnsi(raw, codepage);
|
|
29
|
+
return decoded === void 0 ? void 0 : truncateAtNull(decoded);
|
|
30
|
+
}
|
|
31
|
+
function readUnicodeString(bytes, view, offset) {
|
|
32
|
+
requireBytes(bytes.length, offset, 4, "a UnicodeString's Length field");
|
|
33
|
+
const byteLength = view.getUint32(offset, true) * 2;
|
|
34
|
+
requireBytes(bytes.length, offset + 4, byteLength, "a UnicodeString's Characters field");
|
|
35
|
+
const raw = bytes.subarray(offset + 4, offset + 4 + byteLength);
|
|
36
|
+
return truncateAtNull(UTF16_DECODER.decode(raw));
|
|
37
|
+
}
|
|
38
|
+
function readPropertySetStream(bytes) {
|
|
39
|
+
requireBytes(bytes.length, 0, 48, "the PropertySetStream header");
|
|
40
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
41
|
+
const byteOrder = view.getUint16(0, true);
|
|
42
|
+
if (byteOrder !== 65534) throw new PropertySetFormatError(`property set stream's ByteOrder field is 0x${byteOrder.toString(16)}, not the mandated 0xFFFE`);
|
|
43
|
+
const numPropertySets = view.getUint32(24, true);
|
|
44
|
+
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)`);
|
|
45
|
+
const formatId = readGuid(view, 28);
|
|
46
|
+
const offset0 = view.getUint32(44, true);
|
|
47
|
+
requireBytes(bytes.length, offset0, 8, "the PropertySet packet header");
|
|
48
|
+
const size = view.getUint32(offset0, true);
|
|
49
|
+
requireBytes(bytes.length, offset0, size, "the PropertySet packet's own declared Size");
|
|
50
|
+
const numProperties = view.getUint32(offset0 + 4, true);
|
|
51
|
+
const tableStart = offset0 + 8;
|
|
52
|
+
requireBytes(bytes.length, tableStart, numProperties * 8, "the PropertyIdentifierAndOffset dictionary");
|
|
53
|
+
const entries = [];
|
|
54
|
+
for (let i = 0; i < numProperties; i++) {
|
|
55
|
+
const entryOffset = tableStart + i * 8;
|
|
56
|
+
const pid = view.getUint32(entryOffset, true);
|
|
57
|
+
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");
|
|
58
|
+
entries.push({
|
|
59
|
+
pid,
|
|
60
|
+
relativeOffset: view.getUint32(entryOffset + 4, true)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
let codepage = WINDOWS_1252_CODEPAGE;
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (entry.pid !== 1) continue;
|
|
66
|
+
const abs = offset0 + entry.relativeOffset;
|
|
67
|
+
requireBytes(bytes.length, abs, 8, "the CodePage property's TypedPropertyValue");
|
|
68
|
+
const type = view.getUint16(abs, true);
|
|
69
|
+
if (type !== 2) throw new PropertySetFormatError(`CodePage property (PID 1) has type 0x${type.toString(16)}, not VT_I2 as [MS-OLEPS] requires`);
|
|
70
|
+
const raw = view.getInt16(abs + 4, true);
|
|
71
|
+
codepage = raw < 0 ? raw + 65536 : raw;
|
|
72
|
+
}
|
|
73
|
+
const properties = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const abs = offset0 + entry.relativeOffset;
|
|
76
|
+
requireBytes(bytes.length, abs, 4, "a property's TypedPropertyValue header");
|
|
77
|
+
const type = view.getUint16(abs, true);
|
|
78
|
+
const padding = view.getUint16(abs + 2, true);
|
|
79
|
+
if (padding !== 0) throw new PropertySetFormatError(`property ${entry.pid}'s TypedPropertyValue padding is 0x${padding.toString(16)}, not zero as [MS-OLEPS] requires`);
|
|
80
|
+
const valueOffset = abs + 4;
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 2:
|
|
83
|
+
requireBytes(bytes.length, valueOffset, 4, `property ${entry.pid}'s VT_I2 value`);
|
|
84
|
+
properties.set(entry.pid, {
|
|
85
|
+
type: "VT_I2",
|
|
86
|
+
value: view.getInt16(valueOffset, true)
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
case 3:
|
|
90
|
+
requireBytes(bytes.length, valueOffset, 4, `property ${entry.pid}'s VT_I4 value`);
|
|
91
|
+
properties.set(entry.pid, {
|
|
92
|
+
type: "VT_I4",
|
|
93
|
+
value: view.getInt32(valueOffset, true)
|
|
94
|
+
});
|
|
95
|
+
break;
|
|
96
|
+
case 30: {
|
|
97
|
+
const value = readCodePageString(bytes, view, valueOffset, codepage);
|
|
98
|
+
if (value !== void 0) properties.set(entry.pid, {
|
|
99
|
+
type: "VT_LPSTR",
|
|
100
|
+
value
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case 31: {
|
|
105
|
+
const value = readUnicodeString(bytes, view, valueOffset);
|
|
106
|
+
properties.set(entry.pid, {
|
|
107
|
+
type: "VT_LPWSTR",
|
|
108
|
+
value
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case 64: {
|
|
113
|
+
requireBytes(bytes.length, valueOffset, 8, `property ${entry.pid}'s VT_FILETIME value`);
|
|
114
|
+
const low = view.getUint32(valueOffset, true);
|
|
115
|
+
const high = view.getUint32(valueOffset + 4, true);
|
|
116
|
+
properties.set(entry.pid, {
|
|
117
|
+
type: "VT_FILETIME",
|
|
118
|
+
value: filetimeToDate(low, high)
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
formatId,
|
|
126
|
+
properties
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { PropertySetFormatError, readPropertySetStream };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_oleps_wire = require("./wire.cjs");
|
|
3
|
+
const require_oleps_read = require("./read.cjs");
|
|
4
|
+
const require_oleps_write = require("./write.cjs");
|
|
5
|
+
//#region src/oleps/summary-information.ts
|
|
6
|
+
const FMTID_SUMMARY_INFORMATION = "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}";
|
|
7
|
+
const PID_TITLE = 2;
|
|
8
|
+
const PID_SUBJECT = 3;
|
|
9
|
+
const PID_AUTHOR = 4;
|
|
10
|
+
const PID_KEYWORDS = 5;
|
|
11
|
+
const PID_COMMENTS = 6;
|
|
12
|
+
const PID_LASTPRINTED = 11;
|
|
13
|
+
const PID_CREATE_DTM = 12;
|
|
14
|
+
const PID_LASTSAVE_DTM = 13;
|
|
15
|
+
const KEYWORDS_DELIMITER = ", ";
|
|
16
|
+
function splitKeywords(value) {
|
|
17
|
+
const parts = value.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
18
|
+
return parts.length > 0 ? parts : void 0;
|
|
19
|
+
}
|
|
20
|
+
function stringValue(value, pid) {
|
|
21
|
+
if (value === void 0) return void 0;
|
|
22
|
+
if (value.type !== "VT_LPSTR" && value.type !== "VT_LPWSTR") throw new require_oleps_read.PropertySetFormatError(`SummaryInformation property ${pid} has type ${value.type}, not a string type as [MS-OLEPS]'s SummaryInformation Property Set defines`);
|
|
23
|
+
return value.value.length > 0 ? value.value : void 0;
|
|
24
|
+
}
|
|
25
|
+
const FILETIME_UNSET_ISO = "1601-01-01T00:00:00.000Z";
|
|
26
|
+
function dateIsoValue(value, pid) {
|
|
27
|
+
if (value === void 0) return void 0;
|
|
28
|
+
if (value.type !== "VT_FILETIME") throw new require_oleps_read.PropertySetFormatError(`SummaryInformation property ${pid} has type ${value.type}, not VT_FILETIME as [MS-OLEPS]'s SummaryInformation Property Set defines`);
|
|
29
|
+
const iso = value.value.toISOString();
|
|
30
|
+
return iso === FILETIME_UNSET_ISO ? void 0 : iso;
|
|
31
|
+
}
|
|
32
|
+
function readSummaryInformation(bytes) {
|
|
33
|
+
const propertySet = require_oleps_read.readPropertySetStream(bytes);
|
|
34
|
+
if (propertySet.formatId !== "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}") throw new require_oleps_read.PropertySetFormatError(`property set stream declares FMTID ${propertySet.formatId}, not FMTID_SummaryInformation (${FMTID_SUMMARY_INFORMATION}); this is not a "\\x05SummaryInformation" stream`);
|
|
35
|
+
const { properties } = propertySet;
|
|
36
|
+
const keywords = stringValue(properties.get(PID_KEYWORDS), PID_KEYWORDS);
|
|
37
|
+
return {
|
|
38
|
+
title: stringValue(properties.get(PID_TITLE), PID_TITLE),
|
|
39
|
+
subject: stringValue(properties.get(PID_SUBJECT), PID_SUBJECT),
|
|
40
|
+
author: stringValue(properties.get(PID_AUTHOR), PID_AUTHOR),
|
|
41
|
+
keywords: keywords === void 0 ? void 0 : splitKeywords(keywords),
|
|
42
|
+
comments: stringValue(properties.get(PID_COMMENTS), PID_COMMENTS),
|
|
43
|
+
createdIso: dateIsoValue(properties.get(PID_CREATE_DTM), PID_CREATE_DTM),
|
|
44
|
+
lastSavedIso: dateIsoValue(properties.get(PID_LASTSAVE_DTM), PID_LASTSAVE_DTM),
|
|
45
|
+
lastPrintedIso: dateIsoValue(properties.get(PID_LASTPRINTED), PID_LASTPRINTED)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function writeSummaryInformationStream(properties) {
|
|
49
|
+
const entries = /* @__PURE__ */ new Map();
|
|
50
|
+
entries.set(1, {
|
|
51
|
+
type: "VT_I2",
|
|
52
|
+
value: require_oleps_wire.CP_WINUNICODE
|
|
53
|
+
});
|
|
54
|
+
if (properties.title !== void 0) entries.set(PID_TITLE, {
|
|
55
|
+
type: "VT_LPWSTR",
|
|
56
|
+
value: properties.title
|
|
57
|
+
});
|
|
58
|
+
if (properties.subject !== void 0) entries.set(PID_SUBJECT, {
|
|
59
|
+
type: "VT_LPWSTR",
|
|
60
|
+
value: properties.subject
|
|
61
|
+
});
|
|
62
|
+
if (properties.author !== void 0) entries.set(PID_AUTHOR, {
|
|
63
|
+
type: "VT_LPWSTR",
|
|
64
|
+
value: properties.author
|
|
65
|
+
});
|
|
66
|
+
if (properties.keywords !== void 0 && properties.keywords.length > 0) entries.set(PID_KEYWORDS, {
|
|
67
|
+
type: "VT_LPWSTR",
|
|
68
|
+
value: properties.keywords.join(KEYWORDS_DELIMITER)
|
|
69
|
+
});
|
|
70
|
+
if (properties.comments !== void 0) entries.set(PID_COMMENTS, {
|
|
71
|
+
type: "VT_LPWSTR",
|
|
72
|
+
value: properties.comments
|
|
73
|
+
});
|
|
74
|
+
if (properties.createdIso !== void 0) entries.set(PID_CREATE_DTM, {
|
|
75
|
+
type: "VT_FILETIME",
|
|
76
|
+
value: new Date(properties.createdIso)
|
|
77
|
+
});
|
|
78
|
+
if (properties.lastSavedIso !== void 0) entries.set(PID_LASTSAVE_DTM, {
|
|
79
|
+
type: "VT_FILETIME",
|
|
80
|
+
value: new Date(properties.lastSavedIso)
|
|
81
|
+
});
|
|
82
|
+
if (properties.lastPrintedIso !== void 0) entries.set(PID_LASTPRINTED, {
|
|
83
|
+
type: "VT_FILETIME",
|
|
84
|
+
value: new Date(properties.lastPrintedIso)
|
|
85
|
+
});
|
|
86
|
+
return require_oleps_write.writePropertySetStream({
|
|
87
|
+
formatId: FMTID_SUMMARY_INFORMATION,
|
|
88
|
+
properties: entries
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
exports.FMTID_SUMMARY_INFORMATION = FMTID_SUMMARY_INFORMATION;
|
|
93
|
+
exports.readSummaryInformation = readSummaryInformation;
|
|
94
|
+
exports.writeSummaryInformationStream = writeSummaryInformationStream;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/oleps/summary-information.d.ts
|
|
2
|
+
declare const FMTID_SUMMARY_INFORMATION = "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}";
|
|
3
|
+
interface SummaryInformationProperties {
|
|
4
|
+
readonly title?: string;
|
|
5
|
+
readonly subject?: string;
|
|
6
|
+
readonly author?: string;
|
|
7
|
+
readonly keywords?: readonly string[];
|
|
8
|
+
readonly comments?: string;
|
|
9
|
+
readonly createdIso?: string;
|
|
10
|
+
readonly lastSavedIso?: string;
|
|
11
|
+
readonly lastPrintedIso?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function readSummaryInformation(bytes: Uint8Array<ArrayBuffer>): SummaryInformationProperties;
|
|
14
|
+
declare function writeSummaryInformationStream(properties: SummaryInformationProperties): Uint8Array<ArrayBuffer>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { FMTID_SUMMARY_INFORMATION, SummaryInformationProperties, readSummaryInformation, writeSummaryInformationStream };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/oleps/summary-information.d.ts
|
|
2
|
+
declare const FMTID_SUMMARY_INFORMATION = "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}";
|
|
3
|
+
interface SummaryInformationProperties {
|
|
4
|
+
readonly title?: string;
|
|
5
|
+
readonly subject?: string;
|
|
6
|
+
readonly author?: string;
|
|
7
|
+
readonly keywords?: readonly string[];
|
|
8
|
+
readonly comments?: string;
|
|
9
|
+
readonly createdIso?: string;
|
|
10
|
+
readonly lastSavedIso?: string;
|
|
11
|
+
readonly lastPrintedIso?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function readSummaryInformation(bytes: Uint8Array<ArrayBuffer>): SummaryInformationProperties;
|
|
14
|
+
declare function writeSummaryInformationStream(properties: SummaryInformationProperties): Uint8Array<ArrayBuffer>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { FMTID_SUMMARY_INFORMATION, SummaryInformationProperties, readSummaryInformation, writeSummaryInformationStream };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { CP_WINUNICODE } from "./wire.js";
|
|
2
|
+
import { PropertySetFormatError, readPropertySetStream } from "./read.js";
|
|
3
|
+
import { writePropertySetStream } from "./write.js";
|
|
4
|
+
//#region src/oleps/summary-information.ts
|
|
5
|
+
const FMTID_SUMMARY_INFORMATION = "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}";
|
|
6
|
+
const PID_TITLE = 2;
|
|
7
|
+
const PID_SUBJECT = 3;
|
|
8
|
+
const PID_AUTHOR = 4;
|
|
9
|
+
const PID_KEYWORDS = 5;
|
|
10
|
+
const PID_COMMENTS = 6;
|
|
11
|
+
const PID_LASTPRINTED = 11;
|
|
12
|
+
const PID_CREATE_DTM = 12;
|
|
13
|
+
const PID_LASTSAVE_DTM = 13;
|
|
14
|
+
const KEYWORDS_DELIMITER = ", ";
|
|
15
|
+
function splitKeywords(value) {
|
|
16
|
+
const parts = value.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
17
|
+
return parts.length > 0 ? parts : void 0;
|
|
18
|
+
}
|
|
19
|
+
function stringValue(value, pid) {
|
|
20
|
+
if (value === void 0) return void 0;
|
|
21
|
+
if (value.type !== "VT_LPSTR" && value.type !== "VT_LPWSTR") throw new PropertySetFormatError(`SummaryInformation property ${pid} has type ${value.type}, not a string type as [MS-OLEPS]'s SummaryInformation Property Set defines`);
|
|
22
|
+
return value.value.length > 0 ? value.value : void 0;
|
|
23
|
+
}
|
|
24
|
+
const FILETIME_UNSET_ISO = "1601-01-01T00:00:00.000Z";
|
|
25
|
+
function dateIsoValue(value, pid) {
|
|
26
|
+
if (value === void 0) return void 0;
|
|
27
|
+
if (value.type !== "VT_FILETIME") throw new PropertySetFormatError(`SummaryInformation property ${pid} has type ${value.type}, not VT_FILETIME as [MS-OLEPS]'s SummaryInformation Property Set defines`);
|
|
28
|
+
const iso = value.value.toISOString();
|
|
29
|
+
return iso === FILETIME_UNSET_ISO ? void 0 : iso;
|
|
30
|
+
}
|
|
31
|
+
function readSummaryInformation(bytes) {
|
|
32
|
+
const propertySet = readPropertySetStream(bytes);
|
|
33
|
+
if (propertySet.formatId !== "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}") throw new PropertySetFormatError(`property set stream declares FMTID ${propertySet.formatId}, not FMTID_SummaryInformation (${FMTID_SUMMARY_INFORMATION}); this is not a "\\x05SummaryInformation" stream`);
|
|
34
|
+
const { properties } = propertySet;
|
|
35
|
+
const keywords = stringValue(properties.get(PID_KEYWORDS), PID_KEYWORDS);
|
|
36
|
+
return {
|
|
37
|
+
title: stringValue(properties.get(PID_TITLE), PID_TITLE),
|
|
38
|
+
subject: stringValue(properties.get(PID_SUBJECT), PID_SUBJECT),
|
|
39
|
+
author: stringValue(properties.get(PID_AUTHOR), PID_AUTHOR),
|
|
40
|
+
keywords: keywords === void 0 ? void 0 : splitKeywords(keywords),
|
|
41
|
+
comments: stringValue(properties.get(PID_COMMENTS), PID_COMMENTS),
|
|
42
|
+
createdIso: dateIsoValue(properties.get(PID_CREATE_DTM), PID_CREATE_DTM),
|
|
43
|
+
lastSavedIso: dateIsoValue(properties.get(PID_LASTSAVE_DTM), PID_LASTSAVE_DTM),
|
|
44
|
+
lastPrintedIso: dateIsoValue(properties.get(PID_LASTPRINTED), PID_LASTPRINTED)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function writeSummaryInformationStream(properties) {
|
|
48
|
+
const entries = /* @__PURE__ */ new Map();
|
|
49
|
+
entries.set(1, {
|
|
50
|
+
type: "VT_I2",
|
|
51
|
+
value: CP_WINUNICODE
|
|
52
|
+
});
|
|
53
|
+
if (properties.title !== void 0) entries.set(PID_TITLE, {
|
|
54
|
+
type: "VT_LPWSTR",
|
|
55
|
+
value: properties.title
|
|
56
|
+
});
|
|
57
|
+
if (properties.subject !== void 0) entries.set(PID_SUBJECT, {
|
|
58
|
+
type: "VT_LPWSTR",
|
|
59
|
+
value: properties.subject
|
|
60
|
+
});
|
|
61
|
+
if (properties.author !== void 0) entries.set(PID_AUTHOR, {
|
|
62
|
+
type: "VT_LPWSTR",
|
|
63
|
+
value: properties.author
|
|
64
|
+
});
|
|
65
|
+
if (properties.keywords !== void 0 && properties.keywords.length > 0) entries.set(PID_KEYWORDS, {
|
|
66
|
+
type: "VT_LPWSTR",
|
|
67
|
+
value: properties.keywords.join(KEYWORDS_DELIMITER)
|
|
68
|
+
});
|
|
69
|
+
if (properties.comments !== void 0) entries.set(PID_COMMENTS, {
|
|
70
|
+
type: "VT_LPWSTR",
|
|
71
|
+
value: properties.comments
|
|
72
|
+
});
|
|
73
|
+
if (properties.createdIso !== void 0) entries.set(PID_CREATE_DTM, {
|
|
74
|
+
type: "VT_FILETIME",
|
|
75
|
+
value: new Date(properties.createdIso)
|
|
76
|
+
});
|
|
77
|
+
if (properties.lastSavedIso !== void 0) entries.set(PID_LASTSAVE_DTM, {
|
|
78
|
+
type: "VT_FILETIME",
|
|
79
|
+
value: new Date(properties.lastSavedIso)
|
|
80
|
+
});
|
|
81
|
+
if (properties.lastPrintedIso !== void 0) entries.set(PID_LASTPRINTED, {
|
|
82
|
+
type: "VT_FILETIME",
|
|
83
|
+
value: new Date(properties.lastPrintedIso)
|
|
84
|
+
});
|
|
85
|
+
return writePropertySetStream({
|
|
86
|
+
formatId: FMTID_SUMMARY_INFORMATION,
|
|
87
|
+
properties: entries
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { FMTID_SUMMARY_INFORMATION, readSummaryInformation, writeSummaryInformationStream };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/oleps/wire.ts
|
|
3
|
+
const BYTE_ORDER_MARK = 65534;
|
|
4
|
+
const HEADER_SIZE = 48;
|
|
5
|
+
const PROPERTY_SET_HEADER_SIZE = 8;
|
|
6
|
+
const IDENTIFIER_AND_OFFSET_SIZE = 8;
|
|
7
|
+
const TYPED_VALUE_HEADER_SIZE = 4;
|
|
8
|
+
const VT_I2 = 2;
|
|
9
|
+
const VT_I4 = 3;
|
|
10
|
+
const VT_LPSTR = 30;
|
|
11
|
+
const VT_LPWSTR = 31;
|
|
12
|
+
const VT_FILETIME = 64;
|
|
13
|
+
const PID_DICTIONARY = 0;
|
|
14
|
+
const PID_CODEPAGE = 1;
|
|
15
|
+
const CP_WINUNICODE = 1200;
|
|
16
|
+
const WINDOWS_1252_CODEPAGE = 1252;
|
|
17
|
+
const GUID_NULL = "{00000000-0000-0000-0000-000000000000}";
|
|
18
|
+
function hex(n, width) {
|
|
19
|
+
return n.toString(16).padStart(width, "0");
|
|
20
|
+
}
|
|
21
|
+
function readGuid(view, offset) {
|
|
22
|
+
const data1 = hex(view.getUint32(offset, true), 8);
|
|
23
|
+
const data2 = hex(view.getUint16(offset + 4, true), 4);
|
|
24
|
+
const data3 = hex(view.getUint16(offset + 6, true), 4);
|
|
25
|
+
let data4a = "";
|
|
26
|
+
for (let i = 0; i < 2; i++) data4a += hex(view.getUint8(offset + 8 + i), 2);
|
|
27
|
+
let data4b = "";
|
|
28
|
+
for (let i = 2; i < 8; i++) data4b += hex(view.getUint8(offset + 8 + i), 2);
|
|
29
|
+
return `{${data1}-${data2}-${data3}-${data4a}-${data4b}}`.toUpperCase();
|
|
30
|
+
}
|
|
31
|
+
function writeGuid(view, offset, guid) {
|
|
32
|
+
const digits = guid.replace(/[{}-]/g, "");
|
|
33
|
+
view.setUint32(offset, Number.parseInt(digits.slice(0, 8), 16), true);
|
|
34
|
+
view.setUint16(offset + 4, Number.parseInt(digits.slice(8, 12), 16), true);
|
|
35
|
+
view.setUint16(offset + 6, Number.parseInt(digits.slice(12, 16), 16), true);
|
|
36
|
+
for (let i = 0; i < 8; i++) view.setUint8(offset + 8 + i, Number.parseInt(digits.slice(16 + i * 2, 18 + i * 2), 16));
|
|
37
|
+
}
|
|
38
|
+
const FILETIME_EPOCH_OFFSET_100NS = 116444736000000000n;
|
|
39
|
+
const HUNDRED_NS_PER_MS = 10000n;
|
|
40
|
+
function filetimeToDate(low, high) {
|
|
41
|
+
const ms = ((BigInt(high) << 32n | BigInt(low)) - FILETIME_EPOCH_OFFSET_100NS) / HUNDRED_NS_PER_MS;
|
|
42
|
+
return new Date(Number(ms));
|
|
43
|
+
}
|
|
44
|
+
function dateToFiletime(date) {
|
|
45
|
+
const ticks = BigInt(date.getTime()) * HUNDRED_NS_PER_MS + FILETIME_EPOCH_OFFSET_100NS;
|
|
46
|
+
return {
|
|
47
|
+
low: Number(ticks & 4294967295n),
|
|
48
|
+
high: Number(ticks >> 32n & 4294967295n)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
exports.BYTE_ORDER_MARK = BYTE_ORDER_MARK;
|
|
53
|
+
exports.CP_WINUNICODE = CP_WINUNICODE;
|
|
54
|
+
exports.GUID_NULL = GUID_NULL;
|
|
55
|
+
exports.HEADER_SIZE = HEADER_SIZE;
|
|
56
|
+
exports.IDENTIFIER_AND_OFFSET_SIZE = IDENTIFIER_AND_OFFSET_SIZE;
|
|
57
|
+
exports.PID_CODEPAGE = PID_CODEPAGE;
|
|
58
|
+
exports.PID_DICTIONARY = PID_DICTIONARY;
|
|
59
|
+
exports.PROPERTY_SET_HEADER_SIZE = PROPERTY_SET_HEADER_SIZE;
|
|
60
|
+
exports.TYPED_VALUE_HEADER_SIZE = TYPED_VALUE_HEADER_SIZE;
|
|
61
|
+
exports.VT_FILETIME = VT_FILETIME;
|
|
62
|
+
exports.VT_I2 = VT_I2;
|
|
63
|
+
exports.VT_I4 = VT_I4;
|
|
64
|
+
exports.VT_LPSTR = VT_LPSTR;
|
|
65
|
+
exports.VT_LPWSTR = VT_LPWSTR;
|
|
66
|
+
exports.WINDOWS_1252_CODEPAGE = WINDOWS_1252_CODEPAGE;
|
|
67
|
+
exports.dateToFiletime = dateToFiletime;
|
|
68
|
+
exports.filetimeToDate = filetimeToDate;
|
|
69
|
+
exports.readGuid = readGuid;
|
|
70
|
+
exports.writeGuid = writeGuid;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/oleps/wire.d.ts
|
|
2
|
+
declare const BYTE_ORDER_MARK = 65534;
|
|
3
|
+
declare const HEADER_SIZE = 48;
|
|
4
|
+
declare const PROPERTY_SET_HEADER_SIZE = 8;
|
|
5
|
+
declare const IDENTIFIER_AND_OFFSET_SIZE = 8;
|
|
6
|
+
declare const TYPED_VALUE_HEADER_SIZE = 4;
|
|
7
|
+
declare const VT_I2 = 2;
|
|
8
|
+
declare const VT_I4 = 3;
|
|
9
|
+
declare const VT_LPSTR = 30;
|
|
10
|
+
declare const VT_LPWSTR = 31;
|
|
11
|
+
declare const VT_FILETIME = 64;
|
|
12
|
+
declare const PID_DICTIONARY = 0;
|
|
13
|
+
declare const PID_CODEPAGE = 1;
|
|
14
|
+
declare const CP_WINUNICODE = 1200;
|
|
15
|
+
declare const WINDOWS_1252_CODEPAGE = 1252;
|
|
16
|
+
declare const GUID_NULL = "{00000000-0000-0000-0000-000000000000}";
|
|
17
|
+
declare function readGuid(view: DataView, offset: number): string;
|
|
18
|
+
declare function writeGuid(view: DataView, offset: number, guid: string): void;
|
|
19
|
+
declare function filetimeToDate(low: number, high: number): Date;
|
|
20
|
+
declare function dateToFiletime(date: Date): {
|
|
21
|
+
readonly low: number;
|
|
22
|
+
readonly high: number;
|
|
23
|
+
};
|
|
24
|
+
type PropertyValue = {
|
|
25
|
+
readonly type: "VT_I2";
|
|
26
|
+
readonly value: number;
|
|
27
|
+
} | {
|
|
28
|
+
readonly type: "VT_I4";
|
|
29
|
+
readonly value: number;
|
|
30
|
+
} | {
|
|
31
|
+
readonly type: "VT_LPSTR";
|
|
32
|
+
readonly value: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: "VT_LPWSTR";
|
|
35
|
+
readonly value: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly type: "VT_FILETIME";
|
|
38
|
+
readonly value: Date;
|
|
39
|
+
};
|
|
40
|
+
interface PropertySet {
|
|
41
|
+
readonly formatId: string;
|
|
42
|
+
readonly properties: ReadonlyMap<number, PropertyValue>;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { BYTE_ORDER_MARK, CP_WINUNICODE, GUID_NULL, HEADER_SIZE, IDENTIFIER_AND_OFFSET_SIZE, PID_CODEPAGE, PID_DICTIONARY, PROPERTY_SET_HEADER_SIZE, PropertySet, PropertyValue, TYPED_VALUE_HEADER_SIZE, VT_FILETIME, VT_I2, VT_I4, VT_LPSTR, VT_LPWSTR, WINDOWS_1252_CODEPAGE, dateToFiletime, filetimeToDate, readGuid, writeGuid };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/oleps/wire.d.ts
|
|
2
|
+
declare const BYTE_ORDER_MARK = 65534;
|
|
3
|
+
declare const HEADER_SIZE = 48;
|
|
4
|
+
declare const PROPERTY_SET_HEADER_SIZE = 8;
|
|
5
|
+
declare const IDENTIFIER_AND_OFFSET_SIZE = 8;
|
|
6
|
+
declare const TYPED_VALUE_HEADER_SIZE = 4;
|
|
7
|
+
declare const VT_I2 = 2;
|
|
8
|
+
declare const VT_I4 = 3;
|
|
9
|
+
declare const VT_LPSTR = 30;
|
|
10
|
+
declare const VT_LPWSTR = 31;
|
|
11
|
+
declare const VT_FILETIME = 64;
|
|
12
|
+
declare const PID_DICTIONARY = 0;
|
|
13
|
+
declare const PID_CODEPAGE = 1;
|
|
14
|
+
declare const CP_WINUNICODE = 1200;
|
|
15
|
+
declare const WINDOWS_1252_CODEPAGE = 1252;
|
|
16
|
+
declare const GUID_NULL = "{00000000-0000-0000-0000-000000000000}";
|
|
17
|
+
declare function readGuid(view: DataView, offset: number): string;
|
|
18
|
+
declare function writeGuid(view: DataView, offset: number, guid: string): void;
|
|
19
|
+
declare function filetimeToDate(low: number, high: number): Date;
|
|
20
|
+
declare function dateToFiletime(date: Date): {
|
|
21
|
+
readonly low: number;
|
|
22
|
+
readonly high: number;
|
|
23
|
+
};
|
|
24
|
+
type PropertyValue = {
|
|
25
|
+
readonly type: "VT_I2";
|
|
26
|
+
readonly value: number;
|
|
27
|
+
} | {
|
|
28
|
+
readonly type: "VT_I4";
|
|
29
|
+
readonly value: number;
|
|
30
|
+
} | {
|
|
31
|
+
readonly type: "VT_LPSTR";
|
|
32
|
+
readonly value: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: "VT_LPWSTR";
|
|
35
|
+
readonly value: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly type: "VT_FILETIME";
|
|
38
|
+
readonly value: Date;
|
|
39
|
+
};
|
|
40
|
+
interface PropertySet {
|
|
41
|
+
readonly formatId: string;
|
|
42
|
+
readonly properties: ReadonlyMap<number, PropertyValue>;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { BYTE_ORDER_MARK, CP_WINUNICODE, GUID_NULL, HEADER_SIZE, IDENTIFIER_AND_OFFSET_SIZE, PID_CODEPAGE, PID_DICTIONARY, PROPERTY_SET_HEADER_SIZE, PropertySet, PropertyValue, TYPED_VALUE_HEADER_SIZE, VT_FILETIME, VT_I2, VT_I4, VT_LPSTR, VT_LPWSTR, WINDOWS_1252_CODEPAGE, dateToFiletime, filetimeToDate, readGuid, writeGuid };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/oleps/wire.ts
|
|
2
|
+
const BYTE_ORDER_MARK = 65534;
|
|
3
|
+
const HEADER_SIZE = 48;
|
|
4
|
+
const PROPERTY_SET_HEADER_SIZE = 8;
|
|
5
|
+
const IDENTIFIER_AND_OFFSET_SIZE = 8;
|
|
6
|
+
const TYPED_VALUE_HEADER_SIZE = 4;
|
|
7
|
+
const VT_I2 = 2;
|
|
8
|
+
const VT_I4 = 3;
|
|
9
|
+
const VT_LPSTR = 30;
|
|
10
|
+
const VT_LPWSTR = 31;
|
|
11
|
+
const VT_FILETIME = 64;
|
|
12
|
+
const PID_DICTIONARY = 0;
|
|
13
|
+
const PID_CODEPAGE = 1;
|
|
14
|
+
const CP_WINUNICODE = 1200;
|
|
15
|
+
const WINDOWS_1252_CODEPAGE = 1252;
|
|
16
|
+
const GUID_NULL = "{00000000-0000-0000-0000-000000000000}";
|
|
17
|
+
function hex(n, width) {
|
|
18
|
+
return n.toString(16).padStart(width, "0");
|
|
19
|
+
}
|
|
20
|
+
function readGuid(view, offset) {
|
|
21
|
+
const data1 = hex(view.getUint32(offset, true), 8);
|
|
22
|
+
const data2 = hex(view.getUint16(offset + 4, true), 4);
|
|
23
|
+
const data3 = hex(view.getUint16(offset + 6, true), 4);
|
|
24
|
+
let data4a = "";
|
|
25
|
+
for (let i = 0; i < 2; i++) data4a += hex(view.getUint8(offset + 8 + i), 2);
|
|
26
|
+
let data4b = "";
|
|
27
|
+
for (let i = 2; i < 8; i++) data4b += hex(view.getUint8(offset + 8 + i), 2);
|
|
28
|
+
return `{${data1}-${data2}-${data3}-${data4a}-${data4b}}`.toUpperCase();
|
|
29
|
+
}
|
|
30
|
+
function writeGuid(view, offset, guid) {
|
|
31
|
+
const digits = guid.replace(/[{}-]/g, "");
|
|
32
|
+
view.setUint32(offset, Number.parseInt(digits.slice(0, 8), 16), true);
|
|
33
|
+
view.setUint16(offset + 4, Number.parseInt(digits.slice(8, 12), 16), true);
|
|
34
|
+
view.setUint16(offset + 6, Number.parseInt(digits.slice(12, 16), 16), true);
|
|
35
|
+
for (let i = 0; i < 8; i++) view.setUint8(offset + 8 + i, Number.parseInt(digits.slice(16 + i * 2, 18 + i * 2), 16));
|
|
36
|
+
}
|
|
37
|
+
const FILETIME_EPOCH_OFFSET_100NS = 116444736000000000n;
|
|
38
|
+
const HUNDRED_NS_PER_MS = 10000n;
|
|
39
|
+
function filetimeToDate(low, high) {
|
|
40
|
+
const ms = ((BigInt(high) << 32n | BigInt(low)) - FILETIME_EPOCH_OFFSET_100NS) / HUNDRED_NS_PER_MS;
|
|
41
|
+
return new Date(Number(ms));
|
|
42
|
+
}
|
|
43
|
+
function dateToFiletime(date) {
|
|
44
|
+
const ticks = BigInt(date.getTime()) * HUNDRED_NS_PER_MS + FILETIME_EPOCH_OFFSET_100NS;
|
|
45
|
+
return {
|
|
46
|
+
low: Number(ticks & 4294967295n),
|
|
47
|
+
high: Number(ticks >> 32n & 4294967295n)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { BYTE_ORDER_MARK, CP_WINUNICODE, GUID_NULL, HEADER_SIZE, IDENTIFIER_AND_OFFSET_SIZE, PID_CODEPAGE, PID_DICTIONARY, PROPERTY_SET_HEADER_SIZE, TYPED_VALUE_HEADER_SIZE, VT_FILETIME, VT_I2, VT_I4, VT_LPSTR, VT_LPWSTR, WINDOWS_1252_CODEPAGE, dateToFiletime, filetimeToDate, readGuid, writeGuid };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_oleps_wire = require("./wire.cjs");
|
|
3
|
+
//#region src/oleps/write.ts
|
|
4
|
+
var PropertySetWriteError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "PropertySetWriteError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function encodeUnicodeStringValue(value) {
|
|
11
|
+
const characterBytes = new Uint8Array((value.length + 1) * 2);
|
|
12
|
+
const charView = new DataView(characterBytes.buffer);
|
|
13
|
+
for (let i = 0; i < value.length; i++) charView.setUint16(i * 2, value.charCodeAt(i), true);
|
|
14
|
+
charView.setUint16(value.length * 2, 0, true);
|
|
15
|
+
return characterBytes;
|
|
16
|
+
}
|
|
17
|
+
function padTo4(length) {
|
|
18
|
+
return Math.ceil(length / 4) * 4;
|
|
19
|
+
}
|
|
20
|
+
function encodeTypedPropertyValue(value) {
|
|
21
|
+
switch (value.type) {
|
|
22
|
+
case "VT_I2": {
|
|
23
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
24
|
+
const view = new DataView(bytes.buffer);
|
|
25
|
+
view.setUint16(0, 2, true);
|
|
26
|
+
view.setUint16(2, 0, true);
|
|
27
|
+
view.setInt16(4, value.value, true);
|
|
28
|
+
view.setUint16(6, 0, true);
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
case "VT_I4": {
|
|
32
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
33
|
+
const view = new DataView(bytes.buffer);
|
|
34
|
+
view.setUint16(0, 3, true);
|
|
35
|
+
view.setUint16(2, 0, true);
|
|
36
|
+
view.setInt32(4, value.value, true);
|
|
37
|
+
return bytes;
|
|
38
|
+
}
|
|
39
|
+
case "VT_FILETIME": {
|
|
40
|
+
const { low, high } = require_oleps_wire.dateToFiletime(value.value);
|
|
41
|
+
const bytes = /* @__PURE__ */ new Uint8Array(12);
|
|
42
|
+
const view = new DataView(bytes.buffer);
|
|
43
|
+
view.setUint16(0, 64, true);
|
|
44
|
+
view.setUint16(2, 0, true);
|
|
45
|
+
view.setUint32(4, low, true);
|
|
46
|
+
view.setUint32(8, high, true);
|
|
47
|
+
return bytes;
|
|
48
|
+
}
|
|
49
|
+
case "VT_LPWSTR": {
|
|
50
|
+
const characters = encodeUnicodeStringValue(value.value);
|
|
51
|
+
const paddedLength = padTo4(characters.length);
|
|
52
|
+
const bytes = new Uint8Array(8 + paddedLength);
|
|
53
|
+
const view = new DataView(bytes.buffer);
|
|
54
|
+
view.setUint16(0, 31, true);
|
|
55
|
+
view.setUint16(2, 0, true);
|
|
56
|
+
view.setUint32(4, characters.length / 2, true);
|
|
57
|
+
bytes.set(characters, 8);
|
|
58
|
+
return bytes;
|
|
59
|
+
}
|
|
60
|
+
case "VT_LPSTR": throw new PropertySetWriteError("writePropertySetStream cannot write a VT_LPSTR property: this writer emits Unicode (VT_LPWSTR) strings only, since encoding to an arbitrary ANSI codepage is out of scope -- see the package README's OLEPS scope note");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function writePropertySetStream(propertySet) {
|
|
64
|
+
const entries = [...propertySet.properties.entries()].sort(([a], [b]) => a - b);
|
|
65
|
+
const dictionaryBytes = new Uint8Array(entries.length * 8);
|
|
66
|
+
const dictionaryView = new DataView(dictionaryBytes.buffer);
|
|
67
|
+
const valueChunks = [];
|
|
68
|
+
let valueOffset = 8 + dictionaryBytes.length;
|
|
69
|
+
let index = 0;
|
|
70
|
+
for (const [pid, value] of entries) {
|
|
71
|
+
const encoded = encodeTypedPropertyValue(value);
|
|
72
|
+
dictionaryView.setUint32(index * 8, pid, true);
|
|
73
|
+
dictionaryView.setUint32(index * 8 + 4, valueOffset, true);
|
|
74
|
+
valueChunks.push(encoded);
|
|
75
|
+
valueOffset += encoded.length;
|
|
76
|
+
index += 1;
|
|
77
|
+
}
|
|
78
|
+
const propertySetSize = valueOffset;
|
|
79
|
+
const propertySetBytes = new Uint8Array(propertySetSize);
|
|
80
|
+
const propertySetView = new DataView(propertySetBytes.buffer);
|
|
81
|
+
propertySetView.setUint32(0, propertySetSize, true);
|
|
82
|
+
propertySetView.setUint32(4, entries.length, true);
|
|
83
|
+
propertySetBytes.set(dictionaryBytes, 8);
|
|
84
|
+
let cursor = 8 + dictionaryBytes.length;
|
|
85
|
+
for (const chunk of valueChunks) {
|
|
86
|
+
propertySetBytes.set(chunk, cursor);
|
|
87
|
+
cursor += chunk.length;
|
|
88
|
+
}
|
|
89
|
+
const streamBytes = new Uint8Array(48 + propertySetBytes.length);
|
|
90
|
+
const view = new DataView(streamBytes.buffer);
|
|
91
|
+
view.setUint16(0, require_oleps_wire.BYTE_ORDER_MARK, true);
|
|
92
|
+
view.setUint16(2, 0, true);
|
|
93
|
+
view.setUint32(4, 0, true);
|
|
94
|
+
require_oleps_wire.writeGuid(view, 8, require_oleps_wire.GUID_NULL);
|
|
95
|
+
view.setUint32(24, 1, true);
|
|
96
|
+
require_oleps_wire.writeGuid(view, 28, propertySet.formatId);
|
|
97
|
+
view.setUint32(44, 48, true);
|
|
98
|
+
streamBytes.set(propertySetBytes, 48);
|
|
99
|
+
return streamBytes;
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
exports.PropertySetWriteError = PropertySetWriteError;
|
|
103
|
+
exports.writePropertySetStream = writePropertySetStream;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PropertySet } from "./wire.cjs";
|
|
2
|
+
//#region src/oleps/write.d.ts
|
|
3
|
+
declare class PropertySetWriteError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
declare function writePropertySetStream(propertySet: PropertySet): Uint8Array<ArrayBuffer>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { PropertySetWriteError, writePropertySetStream };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PropertySet } from "./wire.js";
|
|
2
|
+
//#region src/oleps/write.d.ts
|
|
3
|
+
declare class PropertySetWriteError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
declare function writePropertySetStream(propertySet: PropertySet): Uint8Array<ArrayBuffer>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { PropertySetWriteError, writePropertySetStream };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { BYTE_ORDER_MARK, GUID_NULL, dateToFiletime, writeGuid } from "./wire.js";
|
|
2
|
+
//#region src/oleps/write.ts
|
|
3
|
+
var PropertySetWriteError = class extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "PropertySetWriteError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
function encodeUnicodeStringValue(value) {
|
|
10
|
+
const characterBytes = new Uint8Array((value.length + 1) * 2);
|
|
11
|
+
const charView = new DataView(characterBytes.buffer);
|
|
12
|
+
for (let i = 0; i < value.length; i++) charView.setUint16(i * 2, value.charCodeAt(i), true);
|
|
13
|
+
charView.setUint16(value.length * 2, 0, true);
|
|
14
|
+
return characterBytes;
|
|
15
|
+
}
|
|
16
|
+
function padTo4(length) {
|
|
17
|
+
return Math.ceil(length / 4) * 4;
|
|
18
|
+
}
|
|
19
|
+
function encodeTypedPropertyValue(value) {
|
|
20
|
+
switch (value.type) {
|
|
21
|
+
case "VT_I2": {
|
|
22
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
23
|
+
const view = new DataView(bytes.buffer);
|
|
24
|
+
view.setUint16(0, 2, true);
|
|
25
|
+
view.setUint16(2, 0, true);
|
|
26
|
+
view.setInt16(4, value.value, true);
|
|
27
|
+
view.setUint16(6, 0, true);
|
|
28
|
+
return bytes;
|
|
29
|
+
}
|
|
30
|
+
case "VT_I4": {
|
|
31
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
32
|
+
const view = new DataView(bytes.buffer);
|
|
33
|
+
view.setUint16(0, 3, true);
|
|
34
|
+
view.setUint16(2, 0, true);
|
|
35
|
+
view.setInt32(4, value.value, true);
|
|
36
|
+
return bytes;
|
|
37
|
+
}
|
|
38
|
+
case "VT_FILETIME": {
|
|
39
|
+
const { low, high } = dateToFiletime(value.value);
|
|
40
|
+
const bytes = /* @__PURE__ */ new Uint8Array(12);
|
|
41
|
+
const view = new DataView(bytes.buffer);
|
|
42
|
+
view.setUint16(0, 64, true);
|
|
43
|
+
view.setUint16(2, 0, true);
|
|
44
|
+
view.setUint32(4, low, true);
|
|
45
|
+
view.setUint32(8, high, true);
|
|
46
|
+
return bytes;
|
|
47
|
+
}
|
|
48
|
+
case "VT_LPWSTR": {
|
|
49
|
+
const characters = encodeUnicodeStringValue(value.value);
|
|
50
|
+
const paddedLength = padTo4(characters.length);
|
|
51
|
+
const bytes = new Uint8Array(8 + paddedLength);
|
|
52
|
+
const view = new DataView(bytes.buffer);
|
|
53
|
+
view.setUint16(0, 31, true);
|
|
54
|
+
view.setUint16(2, 0, true);
|
|
55
|
+
view.setUint32(4, characters.length / 2, true);
|
|
56
|
+
bytes.set(characters, 8);
|
|
57
|
+
return bytes;
|
|
58
|
+
}
|
|
59
|
+
case "VT_LPSTR": throw new PropertySetWriteError("writePropertySetStream cannot write a VT_LPSTR property: this writer emits Unicode (VT_LPWSTR) strings only, since encoding to an arbitrary ANSI codepage is out of scope -- see the package README's OLEPS scope note");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function writePropertySetStream(propertySet) {
|
|
63
|
+
const entries = [...propertySet.properties.entries()].sort(([a], [b]) => a - b);
|
|
64
|
+
const dictionaryBytes = new Uint8Array(entries.length * 8);
|
|
65
|
+
const dictionaryView = new DataView(dictionaryBytes.buffer);
|
|
66
|
+
const valueChunks = [];
|
|
67
|
+
let valueOffset = 8 + dictionaryBytes.length;
|
|
68
|
+
let index = 0;
|
|
69
|
+
for (const [pid, value] of entries) {
|
|
70
|
+
const encoded = encodeTypedPropertyValue(value);
|
|
71
|
+
dictionaryView.setUint32(index * 8, pid, true);
|
|
72
|
+
dictionaryView.setUint32(index * 8 + 4, valueOffset, true);
|
|
73
|
+
valueChunks.push(encoded);
|
|
74
|
+
valueOffset += encoded.length;
|
|
75
|
+
index += 1;
|
|
76
|
+
}
|
|
77
|
+
const propertySetSize = valueOffset;
|
|
78
|
+
const propertySetBytes = new Uint8Array(propertySetSize);
|
|
79
|
+
const propertySetView = new DataView(propertySetBytes.buffer);
|
|
80
|
+
propertySetView.setUint32(0, propertySetSize, true);
|
|
81
|
+
propertySetView.setUint32(4, entries.length, true);
|
|
82
|
+
propertySetBytes.set(dictionaryBytes, 8);
|
|
83
|
+
let cursor = 8 + dictionaryBytes.length;
|
|
84
|
+
for (const chunk of valueChunks) {
|
|
85
|
+
propertySetBytes.set(chunk, cursor);
|
|
86
|
+
cursor += chunk.length;
|
|
87
|
+
}
|
|
88
|
+
const streamBytes = new Uint8Array(48 + propertySetBytes.length);
|
|
89
|
+
const view = new DataView(streamBytes.buffer);
|
|
90
|
+
view.setUint16(0, BYTE_ORDER_MARK, true);
|
|
91
|
+
view.setUint16(2, 0, true);
|
|
92
|
+
view.setUint32(4, 0, true);
|
|
93
|
+
writeGuid(view, 8, GUID_NULL);
|
|
94
|
+
view.setUint32(24, 1, true);
|
|
95
|
+
writeGuid(view, 28, propertySet.formatId);
|
|
96
|
+
view.setUint32(44, 48, true);
|
|
97
|
+
streamBytes.set(propertySetBytes, 48);
|
|
98
|
+
return streamBytes;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { PropertySetWriteError, writePropertySetStream };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archive-codec",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "ZIP-in-ZIP recursive walking with depth and cumulative decompressed-size guards,
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"description": "ZIP-in-ZIP recursive walking with depth and cumulative decompressed-size guards, bounded classic OLE compound-file ([MS-CFB]) reading and writing, and [MS-OLEPS] Property Set Stream reading and writing - zero document-format knowledge, the archive and container utility package for the documents.js family.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"packageManager": "pnpm@11.6.0",
|
|
69
69
|
"dependencies": {
|
|
70
|
+
"document-schema.js": "^5.6.0",
|
|
70
71
|
"fflate": "^0.8.2"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|