doc-codec 2.3.7 → 2.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.
- package/README.md +21 -2
- package/dist/encryption.cjs +70 -0
- package/dist/encryption.d.cts +13 -0
- package/dist/encryption.d.ts +13 -0
- package/dist/encryption.js +69 -0
- package/dist/fib/fib.cjs +10 -1
- package/dist/fib/fib.d.cts +2 -2
- package/dist/fib/fib.d.ts +2 -2
- package/dist/fib/fib.js +11 -3
- package/dist/fib/offsets.cjs +3 -0
- package/dist/fib/offsets.d.cts +3 -1
- package/dist/fib/offsets.d.ts +3 -1
- package/dist/fib/offsets.js +3 -1
- package/dist/{fib-CtsLtF96.d.cts → fib-DWFta19r.d.cts} +7 -1
- package/dist/{fib-CtsLtF96.d.ts → fib-DWFta19r.d.ts} +7 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/list/numbering-write.d.cts +1 -1
- package/dist/list/numbering-write.d.ts +1 -1
- package/dist/list/numbering.d.cts +1 -1
- package/dist/list/numbering.d.ts +1 -1
- package/dist/{numbering-Bivayss4.d.cts → numbering-DSpm9w6y.d.cts} +1 -1
- package/dist/{numbering-Co4jssbG.d.ts → numbering-DayjnHXY.d.ts} +1 -1
- package/dist/prop/sep.d.cts +1 -1
- package/dist/prop/sep.d.ts +1 -1
- package/dist/read.cjs +21 -11
- package/dist/read.d.cts +4 -4
- package/dist/read.d.ts +4 -4
- package/dist/read.js +23 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Built and shipped, on the read side:
|
|
|
22
22
|
- **`readDocContent`** — the whole chain, producing a `'wordprocessing'` `ContentDocument` of paragraphs, runs and tables.
|
|
23
23
|
- **`isDocBytes`** — distinguishes a `.doc` from the `.xls`, `.ppt` and OLE embeddings that share its container, by looking for a `WordDocument` stream carrying `FibBase.wIdent`.
|
|
24
24
|
- **Document metadata** — `title`/`subject`/`author`/`keywords`/`createdIso`/`modifiedIso` read from a `"\x05SummaryInformation"` stream when one is present (see [Metadata](#metadata)); `comments` and `lastPrintedIso` remain unread, since `LayoutMetadata` has no field for either.
|
|
25
|
+
- **RC4-encrypted documents, decrypted for real (ExaDev/documents.js#1113)** — `readDocContent`/`readDocStreams` take an optional `password`, verified against the Table stream's own `EncryptionHeader` before anything is trusted (see [Encryption](#encryption)). XOR obfuscation and RC4 CryptoAPI stay refused, tracked on [ExaDev/documents.js#922](https://github.com/ExaDev/documents.js/issues/922).
|
|
25
26
|
- **Numbering definitions** — `readDocContent`'s own `numbering` field: every list's glyph/format, level-text template, and start-at value, resolved from `PlfLst`/`PlfLfo` and keyed by the same `listId` a paragraph's `ContentParagraph.list.numId` already carries. See [Numbering definitions](#numbering-definitions), which also covers `writeDocContent`'s own inverse.
|
|
26
27
|
- **The one section's own page size and margins** — `prop/sep.ts`'s `readSectionProperties` resolves `PlcfSed`/`Sepx` (`sprmSXaPage`/`sprmSYaPage`/`sprmSDxaLeft`/`sprmSDxaRight`/`sprmSDyaTop`/`sprmSDyaBottom`) into `ContentSection.pageSize`/`.margins`, falling back field by field to Word's own new-document default (US Letter, one-inch margins) for a document carrying no `PlcfSed` at all, or for any one sprm it leaves unstated. This reader only ever resolves the section spanning the whole main document — the "Section boundaries" row below states what "one section" still means.
|
|
27
28
|
|
|
@@ -41,7 +42,7 @@ Built and shipped, on the write side — see [Writing](#writing) for the full sc
|
|
|
41
42
|
| **Subdocuments** | Only the main document (character positions 0 to `ccpText`) is converted. Footnotes, endnotes, headers, footers, comments and text boxes are not, in either direction. |
|
|
42
43
|
| **Section boundaries** | Section boundaries themselves are not read — the whole document is always treated as one section, spanning the entire main document — so a multi-section `.doc`'s later sections' own page size and margins are never consulted, only the first's (see the read-side page-size/margins bullet above for what is now read of that one section). `writeDocContent` refuses a `ContentDocument` with more than one section, rather than silently merging their content into what would read back as one. |
|
|
43
44
|
| **Extended and user-defined document properties** | `title`/`subject`/`author`/`keywords`/`createdIso`/`modifiedIso` are read from and written to a `"\x05SummaryInformation"` stream when present (see [Metadata](#metadata)); the sibling `"\x05DocumentSummaryInformation"` stream (company, manager, and custom user-defined properties) is not read or written at all. |
|
|
44
|
-
| **Encryption** |
|
|
45
|
+
| **Encryption** | RC4-encrypted documents ([MS-DOC] 2.2.6.2, ExaDev/documents.js#1113) are decrypted given a password -- `readDocContent`/`readDocStreams` take an optional `password`, verified against the Table stream's own EncryptionHeader before anything is trusted (see [Encryption](#encryption) below). XOR obfuscation and RC4 CryptoAPI (a different, newer EncryptionHeader shape) still refuse with a `DocUnsupportedError` rather than being read as plaintext or misread as the wrong scheme. `writeDocContent` never encrypts. |
|
|
45
46
|
| **`sprmPHugePapx` / `sprmPTableProps`** | Paragraph properties stored indirectly in the Data stream are not followed, so such a paragraph reads with fewer properties than it states. [MS-DOC] 2.4.3's own Overview of Tables text names `sprmPTableProps` as a real, legal alternative to `sprmTDefTable` some applications process — but a real producer's row mark is not shown to prefer it: a genuine LibreOffice-authored `.doc` table's own row mark states its TAP through the identical direct `sprmTDefTable` this package's reader and writer already use (confirmed by parsing a LibreOffice 26.2.5.2-authored table's raw `PapxFkp` bytes; see [ExaDev/documents.js#892](https://github.com/ExaDev/documents.js/issues/892)), matching 2.4.3's own compatibility guidance ("An application SHOULD use sprmTDefTable to define table cells for applications that do not process sprmPTableProps"). `writeDocContent` never writes an indirect Papx. |
|
|
46
47
|
| **Hyperlinks and fields** | `ContentRun.hyperlink`, footnote/comment/annotation references, and every other field or anchor character are read as plain text or dropped (see [What is converted](#what-is-converted)) and are not written. |
|
|
47
48
|
| **Every FIB field beyond what this package's own reader needs** | `writeDocContent` populates only the fc/lcb pairs its own reader consults (the style sheet, the two property bin tables, the Clx, the font table). Roughly 140 other `FibRgFcLcb97` pairs — `SttbfAssoc`, `Dop`, the printer-driver structures among them — are left zero, which is the format's own "undefined, MUST be ignored" contract for most of them, but not a certification that every third-party [MS-DOC] reader accepts the result; see `fib/write.ts`'s own note. |
|
|
@@ -170,6 +171,24 @@ The mapping is not 1:1, and each gap is permanent rather than a remaining TODO:
|
|
|
170
171
|
|
|
171
172
|
Only the fixed SummaryInformation property set is read or written — the sibling `"\x05DocumentSummaryInformation"` stream (company, manager, and custom user-defined properties, [MS-OLEPS]'s two-property-set spelling) is not attempted at all, an explicit scope boundary `archive-codec`'s own `oleps` support shares.
|
|
172
173
|
|
|
174
|
+
## Encryption
|
|
175
|
+
|
|
176
|
+
A `.doc` protected with a password to open uses one of three schemes [MS-DOC] 2.2.6 names, selected by `FibBase`'s own `fEncrypted`/`fObfuscated` flags: RC4 encryption ([MS-DOC] 2.2.6.2, this reader), XOR obfuscation (2.2.6.1), or RC4 CryptoAPI (2.2.6.3, a different, newer `EncryptionHeader` shape). `readDocContent`/`readDocStreams` take an optional `password`, ignored for an unencrypted document; a missing or incorrect password against an RC4-encrypted one throws `DocUnsupportedError` rather than returning a partial or garbled document, and so does an XOR-obfuscated or RC4-CryptoAPI-encrypted one regardless of password, since neither is implemented.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { readDocContent } from "doc-codec";
|
|
180
|
+
|
|
181
|
+
const document = readDocContent(bytes, "correct horse battery staple");
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
[MS-DOC] 2.2.6.2's own `EncryptionHeader` _is_ [MS-OFFCRYPTO] 2.3.6.1's RC4 encryption header — byte-identical to what `xls-codec`'s own `FilePass` record carries (ExaDev/documents.js#1108), so `src/encryption.ts` needs no new cryptography, only `archive-codec`'s shared `deriveOfficeRc4BaseHash`/`decryptOfficeRc4` primitives and the doc-specific container layout, which differs from BIFF8's in three real ways rather than being a copy of xls-codec's own scheme:
|
|
185
|
+
|
|
186
|
+
- **Location.** The `EncryptionHeader` sits unencrypted at the very start of the Table stream (`0Table`/`1Table`, whichever `FibBase.fWhichTblStm` selects), its own byte length given by `FibBase.lKey` — unlike `FilePass`, an inline record within the Workbook stream.
|
|
187
|
+
- **Re-keying interval.** 512 bytes, not `xls-codec`'s 1024 (`archive-codec`'s `OFFICE_RC4_DOC_BLOCK_SIZE`) — a real [MS-DOC]-specific value, confirmed against Apache POI's `BinaryRC4Decryptor` independently of `xls-codec`'s own `Biff8DecryptingStream`.
|
|
188
|
+
- **Per-stream block-zero origin.** WordDocument and Table are each encrypted independently, each with its own block-number counter starting at zero at that stream's own byte 0 — unlike `xls-codec`'s single continuous Workbook-stream offset. WordDocument's own unencrypted prefix is a fixed 68 bytes ([MS-DOC] 2.2.6.1/2.2.6.2's own stated literal); Table's is `FibBase.lKey`, since the `EncryptionHeader` occupying it has to be readable before any key can be derived at all. The Data stream is also encrypted in full per the spec, but this reader does not read the Data stream at all today, so decrypting it is out of scope until something needs to.
|
|
189
|
+
|
|
190
|
+
`writeDocContent` never encrypts.
|
|
191
|
+
|
|
173
192
|
## Writing
|
|
174
193
|
|
|
175
194
|
`writeDocContent` takes a `'wordprocessing'` `ContentDocument` with exactly one section, an optional `WriteDocContentOptions`, and produces real [MS-DOC] bytes wrapped in a real [MS-CFB] compound file, inverting every read-side structure listed above: a real piece table (`text/piece-table-write.ts`, always one uncompressed 16-bit piece — see [Why always uncompressed](#why-the-writer-always-writes-uncompressed-text)), `Sprm`-encoded grpprls for each run's and paragraph's own direct formatting (`prop/chp-write.ts`, `prop/pap-write.ts`), `ChpxFkp`/`PapxFkp` pages packed and split across as many 512-byte pages as the content needs (`prop/fkp-write.ts`), a real style sheet minting one entry per distinct `styleId`/`headingLevel` a document's paragraphs actually use (`style/stsh.ts`'s `buildStshForStyles`, [ExaDev/documents.js#1059](https://github.com/ExaDev/documents.js/issues/1059) — identity only, no formatting of a style's own; `FibRgFcLcb97.lcbStshf` "MUST be a nonzero value", so a document is never written without one, even one carrying zero styles, though this package's own reader tolerates a missing one), and a font table when at least one run names a font (`style/fonts.ts`). `WriteDocContentOptions.onWarning`, when given, is called for a non-fatal write-time degradation this writer chooses over throwing — today, only the lost-boundary fallback's own per-row budget check (see [Tables](#tables)'s own note on [ExaDev/documents.js#1013](https://github.com/ExaDev/documents.js/issues/1013)) — but that is not a guarantee the write itself goes on to succeed: a row whose own assigned lost boundaries cannot be trimmed down to a split that fits at all still reports one warning, describing its boundaries as unrecoverable and its fully-unsplit encoding as the fallback being attempted — and, should that unsplit encoding also overflow the row's own byte budget once `buildPapxPages` actually packs it, `writeDocContent` can still throw further down the same pipeline, after any rows still to come have had their own chance to report a warning: a caller can genuinely see this warning followed by a hard failure, but not necessarily right away, and not necessarily for the row whose warning it just read, since `buildPapxPages` packs paragraphs in document order and can fail first on an earlier row that also overflowed. Every genuine refusal this writer makes outright — the kinds named in the table below — still throws `DocFormatError`/`DocUnsupportedError` directly, with no warning first.
|
|
@@ -274,7 +293,7 @@ const written = writeDocContent({
|
|
|
274
293
|
});
|
|
275
294
|
```
|
|
276
295
|
|
|
277
|
-
`readDocContent` throws a `DocFormatError` when the bytes do not conform to [MS-DOC], and a `DocUnsupportedError` when they conform but use a feature this package deliberately refuses rather than approximates (encryption, or the `sprmPChgTabs` sentinel above). `writeDocContent` throws a `DocUnsupportedError` for a document, section count, or block kind outside its own scope (see [Writing](#writing)) and a `DocFormatError` for a value that would need a property out of a sprm's own operand range (a font size or indent too large to fit its 2-byte operand, for instance).
|
|
296
|
+
`readDocContent` throws a `DocFormatError` when the bytes do not conform to [MS-DOC], and a `DocUnsupportedError` when they conform but use a feature this package deliberately refuses rather than approximates (XOR obfuscation or RC4 CryptoAPI encryption, an RC4-encrypted document given no password or the wrong one, or the `sprmPChgTabs` sentinel above). `writeDocContent` throws a `DocUnsupportedError` for a document, section count, or block kind outside its own scope (see [Writing](#writing)) and a `DocFormatError` for a value that would need a property out of a sprm's own operand range (a font size or indent too large to fit its 2-byte operand, for instance).
|
|
278
297
|
|
|
279
298
|
## Worker-isomorphic
|
|
280
299
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_errors = require("./errors.cjs");
|
|
3
|
+
const require_bytes = require("./bytes.cjs");
|
|
4
|
+
require("./fib/offsets.cjs");
|
|
5
|
+
let archive_codec = require("archive-codec");
|
|
6
|
+
//#region src/encryption.ts
|
|
7
|
+
/** [MS-DOC] 2.2.6.2's own EncryptionHeader field layout, byte offsets within the Table stream's own first FibBase.lKey bytes: EncryptionVersionInfo (vMajor/vMinor, 2 bytes each) at 0, then Salt/EncryptedVerifier/EncryptedVerifierHash, each OFFICE_RC4_VERIFIER_LENGTH (16) bytes, back to back. */
|
|
8
|
+
const HEADER_OFFSET = {
|
|
9
|
+
versionMajor: 0,
|
|
10
|
+
versionMinor: 2,
|
|
11
|
+
salt: 4,
|
|
12
|
+
encryptedVerifier: 4 + archive_codec.OFFICE_RC4_VERIFIER_LENGTH,
|
|
13
|
+
encryptedVerifierHash: 4 + archive_codec.OFFICE_RC4_VERIFIER_LENGTH * 2
|
|
14
|
+
};
|
|
15
|
+
/** The RC4 (non-CryptoAPI) EncryptionHeader's own total size -- EncryptionVersionInfo(4) + Salt(16) + EncryptedVerifier(16) + EncryptedVerifierHash(16). Used only to slice the header's own fields out of the Table stream; the actual unencrypted-prefix boundary for decrypting the rest of the Table stream is FibBase.lKey itself (see this file's own top comment), not this constant. */
|
|
16
|
+
const RC4_HEADER_SIZE = 4 + archive_codec.OFFICE_RC4_VERIFIER_LENGTH * 3;
|
|
17
|
+
/** [MS-DOC] 2.2.6.2/2.2.6.1's own literal, stated identically in both sections: the WordDocument stream's initial 68 bytes are never encrypted regardless of scheme. Not further decomposed by the spec into named sub-fields covering exactly this span, so it is carried here as the constant the spec itself states rather than derived from FibBase's own field sizes (which do not sum to 68). */
|
|
18
|
+
const WORD_DOCUMENT_UNENCRYPTED_PREFIX = 68;
|
|
19
|
+
/** [MS-OFFCRYPTO] 2.3.6.1's own EncryptionVersionInfo values naming the plain "RC4 encryption header" this module implements; vMajor 2-4 with vMinor 2 names RC4 CryptoAPI (2.2.6.3) instead, a different header shape this module rejects rather than misreads. */
|
|
20
|
+
const RC4_HEADER_VERSION_MAJOR = 1;
|
|
21
|
+
const RC4_HEADER_VERSION_MINOR = 1;
|
|
22
|
+
/** A bounds-checked subarray that keeps the `Uint8Array<ArrayBuffer>` generic parameter archive-codec's own crypto functions require -- bytes.ts's own `slice` returns a bare `Uint8Array`, which is doc-codec's own convention everywhere else but too wide here, since `md5`/`rc4` construct a `DataView` directly over the buffer and need to know it genuinely is one, not just ArrayBufferLike. */
|
|
23
|
+
function checkedSubarray(bytes, offset, length, what) {
|
|
24
|
+
if (offset + length > bytes.length) throw new require_errors.DocFormatError(`${what} read of ${length} bytes at offset ${offset} runs past the end of a ${bytes.length}-byte stream`);
|
|
25
|
+
return bytes.subarray(offset, offset + length);
|
|
26
|
+
}
|
|
27
|
+
/** Reads the EncryptionHeader from the Table stream's own first RC4_HEADER_SIZE bytes, rejecting RC4 CryptoAPI by its own EncryptionVersionInfo rather than misreading it as the plain RC4 header. */
|
|
28
|
+
function readRc4Header(table) {
|
|
29
|
+
const header = checkedSubarray(table, 0, RC4_HEADER_SIZE, "EncryptionHeader");
|
|
30
|
+
const versionMajor = require_bytes.readUint16LE(header, HEADER_OFFSET.versionMajor);
|
|
31
|
+
const versionMinor = require_bytes.readUint16LE(header, HEADER_OFFSET.versionMinor);
|
|
32
|
+
if (versionMajor !== RC4_HEADER_VERSION_MAJOR || versionMinor !== RC4_HEADER_VERSION_MINOR) throw new require_errors.DocUnsupportedError(`this document uses RC4 CryptoAPI encryption (EncryptionVersionInfo ${versionMajor}.${versionMinor}, [MS-DOC] 2.2.6.3), which this reader does not decrypt`);
|
|
33
|
+
return {
|
|
34
|
+
salt: checkedSubarray(header, HEADER_OFFSET.salt, archive_codec.OFFICE_RC4_VERIFIER_LENGTH, "EncryptionHeader.Salt"),
|
|
35
|
+
encryptedVerifier: checkedSubarray(header, HEADER_OFFSET.encryptedVerifier, archive_codec.OFFICE_RC4_VERIFIER_LENGTH, "EncryptionHeader.EncryptedVerifier"),
|
|
36
|
+
encryptedVerifierHash: checkedSubarray(header, HEADER_OFFSET.encryptedVerifierHash, archive_codec.OFFICE_RC4_VERIFIER_LENGTH, "EncryptionHeader.EncryptedVerifierHash")
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** [MS-OFFCRYPTO] 2.3.6.4's own password verification, identical to xls-codec's own workbook/encryption.ts: block 0's key decrypts EncryptedVerifier, then EncryptedVerifierHash continuing the same keystream, and MD5 of the decrypted verifier must equal the decrypted hash. */
|
|
40
|
+
function verifyPassword(baseHash, header) {
|
|
41
|
+
const decryptedVerifier = (0, archive_codec.decryptOfficeRc4)(baseHash, 0, header.encryptedVerifier, archive_codec.OFFICE_RC4_DOC_BLOCK_SIZE);
|
|
42
|
+
const decryptedVerifierHash = (0, archive_codec.decryptOfficeRc4)(baseHash, archive_codec.OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash, archive_codec.OFFICE_RC4_DOC_BLOCK_SIZE);
|
|
43
|
+
const computedHash = (0, archive_codec.md5)(decryptedVerifier);
|
|
44
|
+
if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new require_errors.DocUnsupportedError("incorrect password for RC4-encrypted document");
|
|
45
|
+
}
|
|
46
|
+
/** Decrypts everything after `prefixLength` bytes of `stream`, leaving the prefix itself untouched -- WORD_DOCUMENT_UNENCRYPTED_PREFIX for WordDocument, FibBase.lKey for Table, each stream's own block-number counter starting fresh at its own byte 0 (this file's own top comment, point 3). */
|
|
47
|
+
function decryptStream(baseHash, stream, prefixLength) {
|
|
48
|
+
const decrypted = new Uint8Array(stream.length);
|
|
49
|
+
decrypted.set(stream.subarray(0, prefixLength), 0);
|
|
50
|
+
decrypted.set((0, archive_codec.decryptOfficeRc4)(baseHash, prefixLength, stream.subarray(prefixLength), archive_codec.OFFICE_RC4_DOC_BLOCK_SIZE), prefixLength);
|
|
51
|
+
return decrypted;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader.
|
|
55
|
+
*
|
|
56
|
+
* Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
|
|
57
|
+
*/
|
|
58
|
+
function decryptDocStreams(wordDocument, table, password) {
|
|
59
|
+
if (password === void 0) throw new require_errors.DocUnsupportedError("this document is RC4-encrypted ([MS-DOC] 2.2.6.2); call readDocContent with a password to decrypt it");
|
|
60
|
+
const header = readRc4Header(table);
|
|
61
|
+
const baseHash = (0, archive_codec.deriveOfficeRc4BaseHash)(password, header.salt);
|
|
62
|
+
verifyPassword(baseHash, header);
|
|
63
|
+
const lKey = require_bytes.readUint32LE(wordDocument, 14);
|
|
64
|
+
return {
|
|
65
|
+
wordDocument: decryptStream(baseHash, wordDocument, WORD_DOCUMENT_UNENCRYPTED_PREFIX),
|
|
66
|
+
table: decryptStream(baseHash, table, lKey)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
exports.decryptDocStreams = decryptDocStreams;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/encryption.d.ts
|
|
2
|
+
interface DecryptedDocStreams {
|
|
3
|
+
readonly wordDocument: Uint8Array<ArrayBuffer>;
|
|
4
|
+
readonly table: Uint8Array<ArrayBuffer>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader.
|
|
8
|
+
*
|
|
9
|
+
* Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
|
|
10
|
+
*/
|
|
11
|
+
declare function decryptDocStreams(wordDocument: Uint8Array<ArrayBuffer>, table: Uint8Array<ArrayBuffer>, password: string | undefined): DecryptedDocStreams;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { DecryptedDocStreams, decryptDocStreams };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/encryption.d.ts
|
|
2
|
+
interface DecryptedDocStreams {
|
|
3
|
+
readonly wordDocument: Uint8Array<ArrayBuffer>;
|
|
4
|
+
readonly table: Uint8Array<ArrayBuffer>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader.
|
|
8
|
+
*
|
|
9
|
+
* Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
|
|
10
|
+
*/
|
|
11
|
+
declare function decryptDocStreams(wordDocument: Uint8Array<ArrayBuffer>, table: Uint8Array<ArrayBuffer>, password: string | undefined): DecryptedDocStreams;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { DecryptedDocStreams, decryptDocStreams };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { DocFormatError, DocUnsupportedError } from "./errors.js";
|
|
2
|
+
import { readUint16LE, readUint32LE } from "./bytes.js";
|
|
3
|
+
import "./fib/offsets.js";
|
|
4
|
+
import { OFFICE_RC4_DOC_BLOCK_SIZE, OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, md5 } from "archive-codec";
|
|
5
|
+
//#region src/encryption.ts
|
|
6
|
+
/** [MS-DOC] 2.2.6.2's own EncryptionHeader field layout, byte offsets within the Table stream's own first FibBase.lKey bytes: EncryptionVersionInfo (vMajor/vMinor, 2 bytes each) at 0, then Salt/EncryptedVerifier/EncryptedVerifierHash, each OFFICE_RC4_VERIFIER_LENGTH (16) bytes, back to back. */
|
|
7
|
+
const HEADER_OFFSET = {
|
|
8
|
+
versionMajor: 0,
|
|
9
|
+
versionMinor: 2,
|
|
10
|
+
salt: 4,
|
|
11
|
+
encryptedVerifier: 4 + OFFICE_RC4_VERIFIER_LENGTH,
|
|
12
|
+
encryptedVerifierHash: 4 + OFFICE_RC4_VERIFIER_LENGTH * 2
|
|
13
|
+
};
|
|
14
|
+
/** The RC4 (non-CryptoAPI) EncryptionHeader's own total size -- EncryptionVersionInfo(4) + Salt(16) + EncryptedVerifier(16) + EncryptedVerifierHash(16). Used only to slice the header's own fields out of the Table stream; the actual unencrypted-prefix boundary for decrypting the rest of the Table stream is FibBase.lKey itself (see this file's own top comment), not this constant. */
|
|
15
|
+
const RC4_HEADER_SIZE = 4 + OFFICE_RC4_VERIFIER_LENGTH * 3;
|
|
16
|
+
/** [MS-DOC] 2.2.6.2/2.2.6.1's own literal, stated identically in both sections: the WordDocument stream's initial 68 bytes are never encrypted regardless of scheme. Not further decomposed by the spec into named sub-fields covering exactly this span, so it is carried here as the constant the spec itself states rather than derived from FibBase's own field sizes (which do not sum to 68). */
|
|
17
|
+
const WORD_DOCUMENT_UNENCRYPTED_PREFIX = 68;
|
|
18
|
+
/** [MS-OFFCRYPTO] 2.3.6.1's own EncryptionVersionInfo values naming the plain "RC4 encryption header" this module implements; vMajor 2-4 with vMinor 2 names RC4 CryptoAPI (2.2.6.3) instead, a different header shape this module rejects rather than misreads. */
|
|
19
|
+
const RC4_HEADER_VERSION_MAJOR = 1;
|
|
20
|
+
const RC4_HEADER_VERSION_MINOR = 1;
|
|
21
|
+
/** A bounds-checked subarray that keeps the `Uint8Array<ArrayBuffer>` generic parameter archive-codec's own crypto functions require -- bytes.ts's own `slice` returns a bare `Uint8Array`, which is doc-codec's own convention everywhere else but too wide here, since `md5`/`rc4` construct a `DataView` directly over the buffer and need to know it genuinely is one, not just ArrayBufferLike. */
|
|
22
|
+
function checkedSubarray(bytes, offset, length, what) {
|
|
23
|
+
if (offset + length > bytes.length) throw new DocFormatError(`${what} read of ${length} bytes at offset ${offset} runs past the end of a ${bytes.length}-byte stream`);
|
|
24
|
+
return bytes.subarray(offset, offset + length);
|
|
25
|
+
}
|
|
26
|
+
/** Reads the EncryptionHeader from the Table stream's own first RC4_HEADER_SIZE bytes, rejecting RC4 CryptoAPI by its own EncryptionVersionInfo rather than misreading it as the plain RC4 header. */
|
|
27
|
+
function readRc4Header(table) {
|
|
28
|
+
const header = checkedSubarray(table, 0, RC4_HEADER_SIZE, "EncryptionHeader");
|
|
29
|
+
const versionMajor = readUint16LE(header, HEADER_OFFSET.versionMajor);
|
|
30
|
+
const versionMinor = readUint16LE(header, HEADER_OFFSET.versionMinor);
|
|
31
|
+
if (versionMajor !== RC4_HEADER_VERSION_MAJOR || versionMinor !== RC4_HEADER_VERSION_MINOR) throw new DocUnsupportedError(`this document uses RC4 CryptoAPI encryption (EncryptionVersionInfo ${versionMajor}.${versionMinor}, [MS-DOC] 2.2.6.3), which this reader does not decrypt`);
|
|
32
|
+
return {
|
|
33
|
+
salt: checkedSubarray(header, HEADER_OFFSET.salt, OFFICE_RC4_VERIFIER_LENGTH, "EncryptionHeader.Salt"),
|
|
34
|
+
encryptedVerifier: checkedSubarray(header, HEADER_OFFSET.encryptedVerifier, OFFICE_RC4_VERIFIER_LENGTH, "EncryptionHeader.EncryptedVerifier"),
|
|
35
|
+
encryptedVerifierHash: checkedSubarray(header, HEADER_OFFSET.encryptedVerifierHash, OFFICE_RC4_VERIFIER_LENGTH, "EncryptionHeader.EncryptedVerifierHash")
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** [MS-OFFCRYPTO] 2.3.6.4's own password verification, identical to xls-codec's own workbook/encryption.ts: block 0's key decrypts EncryptedVerifier, then EncryptedVerifierHash continuing the same keystream, and MD5 of the decrypted verifier must equal the decrypted hash. */
|
|
39
|
+
function verifyPassword(baseHash, header) {
|
|
40
|
+
const decryptedVerifier = decryptOfficeRc4(baseHash, 0, header.encryptedVerifier, OFFICE_RC4_DOC_BLOCK_SIZE);
|
|
41
|
+
const decryptedVerifierHash = decryptOfficeRc4(baseHash, OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash, OFFICE_RC4_DOC_BLOCK_SIZE);
|
|
42
|
+
const computedHash = md5(decryptedVerifier);
|
|
43
|
+
if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new DocUnsupportedError("incorrect password for RC4-encrypted document");
|
|
44
|
+
}
|
|
45
|
+
/** Decrypts everything after `prefixLength` bytes of `stream`, leaving the prefix itself untouched -- WORD_DOCUMENT_UNENCRYPTED_PREFIX for WordDocument, FibBase.lKey for Table, each stream's own block-number counter starting fresh at its own byte 0 (this file's own top comment, point 3). */
|
|
46
|
+
function decryptStream(baseHash, stream, prefixLength) {
|
|
47
|
+
const decrypted = new Uint8Array(stream.length);
|
|
48
|
+
decrypted.set(stream.subarray(0, prefixLength), 0);
|
|
49
|
+
decrypted.set(decryptOfficeRc4(baseHash, prefixLength, stream.subarray(prefixLength), OFFICE_RC4_DOC_BLOCK_SIZE), prefixLength);
|
|
50
|
+
return decrypted;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Decrypts an RC4-encrypted (fEncrypted=1, fObfuscated=0) document's WordDocument and Table streams given the password, verifying it first against the Table stream's own EncryptionHeader.
|
|
54
|
+
*
|
|
55
|
+
* Throws `DocUnsupportedError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
|
|
56
|
+
*/
|
|
57
|
+
function decryptDocStreams(wordDocument, table, password) {
|
|
58
|
+
if (password === void 0) throw new DocUnsupportedError("this document is RC4-encrypted ([MS-DOC] 2.2.6.2); call readDocContent with a password to decrypt it");
|
|
59
|
+
const header = readRc4Header(table);
|
|
60
|
+
const baseHash = deriveOfficeRc4BaseHash(password, header.salt);
|
|
61
|
+
verifyPassword(baseHash, header);
|
|
62
|
+
const lKey = readUint32LE(wordDocument, 14);
|
|
63
|
+
return {
|
|
64
|
+
wordDocument: decryptStream(baseHash, wordDocument, WORD_DOCUMENT_UNENCRYPTED_PREFIX),
|
|
65
|
+
table: decryptStream(baseHash, table, lKey)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { decryptDocStreams };
|
package/dist/fib/fib.cjs
CHANGED
|
@@ -3,12 +3,20 @@ const require_errors = require("../errors.cjs");
|
|
|
3
3
|
const require_bytes = require("../bytes.cjs");
|
|
4
4
|
const require_fib_offsets = require("./offsets.cjs");
|
|
5
5
|
//#region src/fib/fib.ts
|
|
6
|
+
/** FibBase's own encryption-related flags and stream selector, [MS-DOC] 2.5.2 -- every one of them sits within the 68-byte prefix [MS-DOC] 2.2.6.2 leaves unencrypted, so this is always safely readable regardless of whether the document is actually encrypted, unlike parseFib's own later reads. read.ts calls this before choosing whether to decrypt and which Table stream to read, since both decisions have to be made before parseFib can run on a genuinely encrypted document -- parseFib itself no longer checks fEncrypted at all; a caller reading raw, still-encrypted bytes into it is a caller bug read.ts's own orchestration exists to prevent. */
|
|
7
|
+
function peekFibBaseFlags(wordDocument) {
|
|
8
|
+
const flags = require_bytes.readUint16LE(wordDocument, 10);
|
|
9
|
+
return {
|
|
10
|
+
fEncrypted: (flags & require_fib_offsets.FIB_BASE_FLAG.fEncrypted) !== 0,
|
|
11
|
+
fObfuscated: (flags & require_fib_offsets.FIB_BASE_FLAG.fObfuscated) !== 0,
|
|
12
|
+
fWhichTblStm: (flags & require_fib_offsets.FIB_BASE_FLAG.fWhichTblStm) !== 0 ? 1 : 0
|
|
13
|
+
};
|
|
14
|
+
}
|
|
6
15
|
function parseFib(wordDocument) {
|
|
7
16
|
const wIdent = require_bytes.readUint16LE(wordDocument, 0);
|
|
8
17
|
if (wIdent !== 42476) throw new require_errors.DocFormatError(`the WordDocument stream begins with 0x${wIdent.toString(16).toUpperCase().padStart(4, "0")} rather than the 0xA5EC every Word Binary File's FibBase.wIdent must carry`);
|
|
9
18
|
const nFib = require_bytes.readUint16LE(wordDocument, 2);
|
|
10
19
|
const flags = require_bytes.readUint16LE(wordDocument, 10);
|
|
11
|
-
if ((flags & require_fib_offsets.FIB_BASE_FLAG.fEncrypted) !== 0) throw new require_errors.DocUnsupportedError((flags & require_fib_offsets.FIB_BASE_FLAG.fObfuscated) !== 0 ? "this document is XOR-obfuscated ([MS-DOC] 2.2.6.1); doc-codec cannot decrypt it, and reading its streams as plaintext would produce arbitrary text rather than the document's own" : "this document is encrypted ([MS-DOC] 2.2.6); doc-codec cannot decrypt it, and reading its streams as plaintext would produce arbitrary text rather than the document's own");
|
|
12
20
|
const csw = require_bytes.readUint16LE(wordDocument, 32);
|
|
13
21
|
if (csw !== 14) throw new require_errors.DocFormatError(`Fib.csw is 0x${csw.toString(16)} rather than the mandated 0x${14 .toString(16)}, so FibRgW97 is not the size every later offset assumes`);
|
|
14
22
|
const cslw = require_bytes.readUint16LE(wordDocument, 62);
|
|
@@ -55,4 +63,5 @@ function tableStreamName(fib) {
|
|
|
55
63
|
}
|
|
56
64
|
//#endregion
|
|
57
65
|
exports.parseFib = parseFib;
|
|
66
|
+
exports.peekFibBaseFlags = peekFibBaseFlags;
|
|
58
67
|
exports.tableStreamName = tableStreamName;
|
package/dist/fib/fib.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as parseFib, r as
|
|
2
|
-
export { Fib, parseFib, tableStreamName };
|
|
1
|
+
import { i as tableStreamName, n as parseFib, r as peekFibBaseFlags, t as Fib } from "../fib-DWFta19r.cjs";
|
|
2
|
+
export { Fib, parseFib, peekFibBaseFlags, tableStreamName };
|
package/dist/fib/fib.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as parseFib, r as
|
|
2
|
-
export { Fib, parseFib, tableStreamName };
|
|
1
|
+
import { i as tableStreamName, n as parseFib, r as peekFibBaseFlags, t as Fib } from "../fib-DWFta19r.js";
|
|
2
|
+
export { Fib, parseFib, peekFibBaseFlags, tableStreamName };
|
package/dist/fib/fib.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { DocFormatError
|
|
1
|
+
import { DocFormatError } from "../errors.js";
|
|
2
2
|
import { readInt32LE, readUint16LE, readUint32LE } from "../bytes.js";
|
|
3
3
|
import { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, LW_OFFSET } from "./offsets.js";
|
|
4
4
|
//#region src/fib/fib.ts
|
|
5
|
+
/** FibBase's own encryption-related flags and stream selector, [MS-DOC] 2.5.2 -- every one of them sits within the 68-byte prefix [MS-DOC] 2.2.6.2 leaves unencrypted, so this is always safely readable regardless of whether the document is actually encrypted, unlike parseFib's own later reads. read.ts calls this before choosing whether to decrypt and which Table stream to read, since both decisions have to be made before parseFib can run on a genuinely encrypted document -- parseFib itself no longer checks fEncrypted at all; a caller reading raw, still-encrypted bytes into it is a caller bug read.ts's own orchestration exists to prevent. */
|
|
6
|
+
function peekFibBaseFlags(wordDocument) {
|
|
7
|
+
const flags = readUint16LE(wordDocument, 10);
|
|
8
|
+
return {
|
|
9
|
+
fEncrypted: (flags & FIB_BASE_FLAG.fEncrypted) !== 0,
|
|
10
|
+
fObfuscated: (flags & FIB_BASE_FLAG.fObfuscated) !== 0,
|
|
11
|
+
fWhichTblStm: (flags & FIB_BASE_FLAG.fWhichTblStm) !== 0 ? 1 : 0
|
|
12
|
+
};
|
|
13
|
+
}
|
|
5
14
|
function parseFib(wordDocument) {
|
|
6
15
|
const wIdent = readUint16LE(wordDocument, 0);
|
|
7
16
|
if (wIdent !== 42476) throw new DocFormatError(`the WordDocument stream begins with 0x${wIdent.toString(16).toUpperCase().padStart(4, "0")} rather than the 0xA5EC every Word Binary File's FibBase.wIdent must carry`);
|
|
8
17
|
const nFib = readUint16LE(wordDocument, 2);
|
|
9
18
|
const flags = readUint16LE(wordDocument, 10);
|
|
10
|
-
if ((flags & FIB_BASE_FLAG.fEncrypted) !== 0) throw new DocUnsupportedError((flags & FIB_BASE_FLAG.fObfuscated) !== 0 ? "this document is XOR-obfuscated ([MS-DOC] 2.2.6.1); doc-codec cannot decrypt it, and reading its streams as plaintext would produce arbitrary text rather than the document's own" : "this document is encrypted ([MS-DOC] 2.2.6); doc-codec cannot decrypt it, and reading its streams as plaintext would produce arbitrary text rather than the document's own");
|
|
11
19
|
const csw = readUint16LE(wordDocument, 32);
|
|
12
20
|
if (csw !== 14) throw new DocFormatError(`Fib.csw is 0x${csw.toString(16)} rather than the mandated 0x${14 .toString(16)}, so FibRgW97 is not the size every later offset assumes`);
|
|
13
21
|
const cslw = readUint16LE(wordDocument, 62);
|
|
@@ -53,4 +61,4 @@ function tableStreamName(fib) {
|
|
|
53
61
|
return fib.fWhichTblStm === 1 ? "1Table" : "0Table";
|
|
54
62
|
}
|
|
55
63
|
//#endregion
|
|
56
|
-
export { parseFib, tableStreamName };
|
|
64
|
+
export { parseFib, peekFibBaseFlags, tableStreamName };
|
package/dist/fib/offsets.cjs
CHANGED
|
@@ -2,6 +2,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
//#region src/fib/offsets.ts
|
|
3
3
|
/** FibBase, [MS-DOC] 2.5.2 -- the fixed 32-byte head of every Fib. csw, the count of 16-bit values in FibRgW97, sits immediately after it, so this doubles as csw's own offset. */
|
|
4
4
|
const FIB_BASE_SIZE = 32;
|
|
5
|
+
/** FibBase.lKey, [MS-DOC] 2.5.2 -- a 4-byte field at offset 14 (after wIdent, nFib, unused, lid, pnNext, and the flags word), meaningful only when fEncrypted is 1: the XOR obfuscation password verifier when fObfuscated is also 1, or the byte size of the EncryptionHeader stored at the start of the Table stream when fObfuscated is 0 ([MS-DOC] 2.2.6.2/encryption.ts). Otherwise MUST be 0. Sits within the 68-byte prefix [MS-DOC] 2.2.6.2 leaves unencrypted, so it is always safely readable regardless of the document's own encryption status. */
|
|
6
|
+
const FIB_LKEY_OFFSET = 14;
|
|
5
7
|
/** csw MUST be 0x000E, fixing FibRgW97 at 28 bytes and every offset after it. */
|
|
6
8
|
const FIB_CSW_REQUIRED = 14;
|
|
7
9
|
/** FibRgW97, [MS-DOC] 2.5.3 -- 14 16-bit values, none of which this reader needs. */
|
|
@@ -61,6 +63,7 @@ exports.FIB_CSLW_OFFSET = FIB_CSLW_OFFSET;
|
|
|
61
63
|
exports.FIB_CSLW_REQUIRED = FIB_CSLW_REQUIRED;
|
|
62
64
|
exports.FIB_CSW_REQUIRED = FIB_CSW_REQUIRED;
|
|
63
65
|
exports.FIB_FC_LCB_BLOB_OFFSET = FIB_FC_LCB_BLOB_OFFSET;
|
|
66
|
+
exports.FIB_LKEY_OFFSET = FIB_LKEY_OFFSET;
|
|
64
67
|
exports.FIB_RG_LW_OFFSET = FIB_RG_LW_OFFSET;
|
|
65
68
|
exports.FIB_RG_LW_SIZE = FIB_RG_LW_SIZE;
|
|
66
69
|
exports.FIB_RG_W_OFFSET = FIB_RG_W_OFFSET;
|
package/dist/fib/offsets.d.cts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
//#region src/fib/offsets.d.ts
|
|
2
2
|
/** FibBase, [MS-DOC] 2.5.2 -- the fixed 32-byte head of every Fib. csw, the count of 16-bit values in FibRgW97, sits immediately after it, so this doubles as csw's own offset. */
|
|
3
3
|
declare const FIB_BASE_SIZE = 32;
|
|
4
|
+
/** FibBase.lKey, [MS-DOC] 2.5.2 -- a 4-byte field at offset 14 (after wIdent, nFib, unused, lid, pnNext, and the flags word), meaningful only when fEncrypted is 1: the XOR obfuscation password verifier when fObfuscated is also 1, or the byte size of the EncryptionHeader stored at the start of the Table stream when fObfuscated is 0 ([MS-DOC] 2.2.6.2/encryption.ts). Otherwise MUST be 0. Sits within the 68-byte prefix [MS-DOC] 2.2.6.2 leaves unencrypted, so it is always safely readable regardless of the document's own encryption status. */
|
|
5
|
+
declare const FIB_LKEY_OFFSET = 14;
|
|
4
6
|
/** csw MUST be 0x000E, fixing FibRgW97 at 28 bytes and every offset after it. */
|
|
5
7
|
declare const FIB_CSW_REQUIRED = 14;
|
|
6
8
|
/** FibRgW97, [MS-DOC] 2.5.3 -- 14 16-bit values, none of which this reader needs. */
|
|
@@ -52,4 +54,4 @@ declare const FIB_BASE_FLAG: {
|
|
|
52
54
|
};
|
|
53
55
|
declare const FIB_W_IDENT = 42476;
|
|
54
56
|
//#endregion
|
|
55
|
-
export { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET };
|
|
57
|
+
export { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET };
|
package/dist/fib/offsets.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
//#region src/fib/offsets.d.ts
|
|
2
2
|
/** FibBase, [MS-DOC] 2.5.2 -- the fixed 32-byte head of every Fib. csw, the count of 16-bit values in FibRgW97, sits immediately after it, so this doubles as csw's own offset. */
|
|
3
3
|
declare const FIB_BASE_SIZE = 32;
|
|
4
|
+
/** FibBase.lKey, [MS-DOC] 2.5.2 -- a 4-byte field at offset 14 (after wIdent, nFib, unused, lid, pnNext, and the flags word), meaningful only when fEncrypted is 1: the XOR obfuscation password verifier when fObfuscated is also 1, or the byte size of the EncryptionHeader stored at the start of the Table stream when fObfuscated is 0 ([MS-DOC] 2.2.6.2/encryption.ts). Otherwise MUST be 0. Sits within the 68-byte prefix [MS-DOC] 2.2.6.2 leaves unencrypted, so it is always safely readable regardless of the document's own encryption status. */
|
|
5
|
+
declare const FIB_LKEY_OFFSET = 14;
|
|
4
6
|
/** csw MUST be 0x000E, fixing FibRgW97 at 28 bytes and every offset after it. */
|
|
5
7
|
declare const FIB_CSW_REQUIRED = 14;
|
|
6
8
|
/** FibRgW97, [MS-DOC] 2.5.3 -- 14 16-bit values, none of which this reader needs. */
|
|
@@ -52,4 +54,4 @@ declare const FIB_BASE_FLAG: {
|
|
|
52
54
|
};
|
|
53
55
|
declare const FIB_W_IDENT = 42476;
|
|
54
56
|
//#endregion
|
|
55
|
-
export { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET };
|
|
57
|
+
export { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET };
|
package/dist/fib/offsets.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
//#region src/fib/offsets.ts
|
|
2
2
|
/** FibBase, [MS-DOC] 2.5.2 -- the fixed 32-byte head of every Fib. csw, the count of 16-bit values in FibRgW97, sits immediately after it, so this doubles as csw's own offset. */
|
|
3
3
|
const FIB_BASE_SIZE = 32;
|
|
4
|
+
/** FibBase.lKey, [MS-DOC] 2.5.2 -- a 4-byte field at offset 14 (after wIdent, nFib, unused, lid, pnNext, and the flags word), meaningful only when fEncrypted is 1: the XOR obfuscation password verifier when fObfuscated is also 1, or the byte size of the EncryptionHeader stored at the start of the Table stream when fObfuscated is 0 ([MS-DOC] 2.2.6.2/encryption.ts). Otherwise MUST be 0. Sits within the 68-byte prefix [MS-DOC] 2.2.6.2 leaves unencrypted, so it is always safely readable regardless of the document's own encryption status. */
|
|
5
|
+
const FIB_LKEY_OFFSET = 14;
|
|
4
6
|
/** csw MUST be 0x000E, fixing FibRgW97 at 28 bytes and every offset after it. */
|
|
5
7
|
const FIB_CSW_REQUIRED = 14;
|
|
6
8
|
/** FibRgW97, [MS-DOC] 2.5.3 -- 14 16-bit values, none of which this reader needs. */
|
|
@@ -52,4 +54,4 @@ const FIB_BASE_FLAG = {
|
|
|
52
54
|
};
|
|
53
55
|
const FIB_W_IDENT = 42476;
|
|
54
56
|
//#endregion
|
|
55
|
-
export { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET };
|
|
57
|
+
export { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET };
|
|
@@ -30,8 +30,14 @@ interface Fib {
|
|
|
30
30
|
readonly fcPlfLfo: number;
|
|
31
31
|
readonly lcbPlfLfo: number;
|
|
32
32
|
}
|
|
33
|
+
/** FibBase's own encryption-related flags and stream selector, [MS-DOC] 2.5.2 -- every one of them sits within the 68-byte prefix [MS-DOC] 2.2.6.2 leaves unencrypted, so this is always safely readable regardless of whether the document is actually encrypted, unlike parseFib's own later reads. read.ts calls this before choosing whether to decrypt and which Table stream to read, since both decisions have to be made before parseFib can run on a genuinely encrypted document -- parseFib itself no longer checks fEncrypted at all; a caller reading raw, still-encrypted bytes into it is a caller bug read.ts's own orchestration exists to prevent. */
|
|
34
|
+
declare function peekFibBaseFlags(wordDocument: Uint8Array): {
|
|
35
|
+
readonly fEncrypted: boolean;
|
|
36
|
+
readonly fObfuscated: boolean;
|
|
37
|
+
readonly fWhichTblStm: 0 | 1;
|
|
38
|
+
};
|
|
33
39
|
declare function parseFib(wordDocument: Uint8Array): Fib;
|
|
34
40
|
/** The name of the compound-file stream every `fc` offset in the Fib is relative to, selected by FibBase.fWhichTblStm. */
|
|
35
41
|
declare function tableStreamName(fib: Fib): "0Table" | "1Table";
|
|
36
42
|
//#endregion
|
|
37
|
-
export { parseFib as n,
|
|
43
|
+
export { tableStreamName as i, parseFib as n, peekFibBaseFlags as r, Fib as t };
|
|
@@ -30,8 +30,14 @@ interface Fib {
|
|
|
30
30
|
readonly fcPlfLfo: number;
|
|
31
31
|
readonly lcbPlfLfo: number;
|
|
32
32
|
}
|
|
33
|
+
/** FibBase's own encryption-related flags and stream selector, [MS-DOC] 2.5.2 -- every one of them sits within the 68-byte prefix [MS-DOC] 2.2.6.2 leaves unencrypted, so this is always safely readable regardless of whether the document is actually encrypted, unlike parseFib's own later reads. read.ts calls this before choosing whether to decrypt and which Table stream to read, since both decisions have to be made before parseFib can run on a genuinely encrypted document -- parseFib itself no longer checks fEncrypted at all; a caller reading raw, still-encrypted bytes into it is a caller bug read.ts's own orchestration exists to prevent. */
|
|
34
|
+
declare function peekFibBaseFlags(wordDocument: Uint8Array): {
|
|
35
|
+
readonly fEncrypted: boolean;
|
|
36
|
+
readonly fObfuscated: boolean;
|
|
37
|
+
readonly fWhichTblStm: 0 | 1;
|
|
38
|
+
};
|
|
33
39
|
declare function parseFib(wordDocument: Uint8Array): Fib;
|
|
34
40
|
/** The name of the compound-file stream every `fc` offset in the Fib is relative to, selected by FibBase.fWhichTblStm. */
|
|
35
41
|
declare function tableStreamName(fib: Fib): "0Table" | "1Table";
|
|
36
42
|
//#endregion
|
|
37
|
-
export { parseFib as n,
|
|
43
|
+
export { tableStreamName as i, parseFib as n, peekFibBaseFlags as r, Fib as t };
|
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ exports.FIB_CSLW_OFFSET = require_fib_offsets.FIB_CSLW_OFFSET;
|
|
|
39
39
|
exports.FIB_CSLW_REQUIRED = require_fib_offsets.FIB_CSLW_REQUIRED;
|
|
40
40
|
exports.FIB_CSW_REQUIRED = require_fib_offsets.FIB_CSW_REQUIRED;
|
|
41
41
|
exports.FIB_FC_LCB_BLOB_OFFSET = require_fib_offsets.FIB_FC_LCB_BLOB_OFFSET;
|
|
42
|
+
exports.FIB_LKEY_OFFSET = require_fib_offsets.FIB_LKEY_OFFSET;
|
|
42
43
|
exports.FIB_RG_LW_OFFSET = require_fib_offsets.FIB_RG_LW_OFFSET;
|
|
43
44
|
exports.FIB_RG_LW_SIZE = require_fib_offsets.FIB_RG_LW_SIZE;
|
|
44
45
|
exports.FIB_RG_W_OFFSET = require_fib_offsets.FIB_RG_W_OFFSET;
|
|
@@ -97,6 +98,7 @@ exports.parseFontTable = require_style_fonts.parseFontTable;
|
|
|
97
98
|
exports.parsePapxFkp = require_prop_fkp.parsePapxFkp;
|
|
98
99
|
exports.parsePlc = require_plc.parsePlc;
|
|
99
100
|
exports.parseStsh = require_style_stsh.parseStsh;
|
|
101
|
+
exports.peekFibBaseFlags = require_fib_fib.peekFibBaseFlags;
|
|
100
102
|
exports.readDocContent = require_read.readDocContent;
|
|
101
103
|
exports.readDocStreams = require_read.readDocStreams;
|
|
102
104
|
exports.readGrpprl = require_prop_sprm.readGrpprl;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readInt16LE, readInt32LE, readUint16LE, readUint32LE, readUint8, slice } from "./bytes.cjs";
|
|
2
2
|
import { SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM, isDocBytes } from "./detect.cjs";
|
|
3
3
|
import { DocFormatError, DocUnsupportedError } from "./errors.cjs";
|
|
4
|
-
import { n as parseFib, r as
|
|
5
|
-
import { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET } from "./fib/offsets.cjs";
|
|
4
|
+
import { i as tableStreamName, n as parseFib, r as peekFibBaseFlags, t as Fib } from "./fib-DWFta19r.cjs";
|
|
5
|
+
import { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET } from "./fib/offsets.cjs";
|
|
6
6
|
import { FibWriteSpec, buildFib } from "./fib/write.cjs";
|
|
7
7
|
import { Plc, findLargestAtMost, parsePlc } from "./plc.cjs";
|
|
8
8
|
import { layoutMetadataToSummaryInformation } from "./metadata.cjs";
|
|
@@ -21,7 +21,7 @@ import { SectionProperties, applySectionSprms, readSectionProperties } from "./p
|
|
|
21
21
|
import { buildPlcfSed, buildSepx, encodeSectionGrpprl } from "./prop/sep-write.cjs";
|
|
22
22
|
import { ResolvedStyleFormatting, STI_USER_DEFINED, STK, Style, StyleSheet, buildStshForStyles, headingLevelFromIstd, parseStsh, resolveStyleFormatting } from "./style/stsh.cjs";
|
|
23
23
|
import { buildFontTable, parseFontTable } from "./style/fonts.cjs";
|
|
24
|
-
import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "./numbering-
|
|
24
|
+
import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "./numbering-DSpm9w6y.cjs";
|
|
25
25
|
import { DocContent, DocStreams, ParagraphEntry, readDocContent, readDocStreams } from "./read.cjs";
|
|
26
26
|
import { WriteDocContentOptions, writeDocContent } from "./write.cjs";
|
|
27
|
-
export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, DRAWN_OBJECT, DocContent, DocFormatError, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, ResolvedStyleFormatting, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, tableStreamName, writeDocContent };
|
|
27
|
+
export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, DRAWN_OBJECT, DocContent, DocFormatError, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, ResolvedStyleFormatting, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, tableStreamName, writeDocContent };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readInt16LE, readInt32LE, readUint16LE, readUint32LE, readUint8, slice } from "./bytes.js";
|
|
2
2
|
import { SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM, isDocBytes } from "./detect.js";
|
|
3
3
|
import { DocFormatError, DocUnsupportedError } from "./errors.js";
|
|
4
|
-
import { n as parseFib, r as
|
|
5
|
-
import { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET } from "./fib/offsets.js";
|
|
4
|
+
import { i as tableStreamName, n as parseFib, r as peekFibBaseFlags, t as Fib } from "./fib-DWFta19r.js";
|
|
5
|
+
import { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET } from "./fib/offsets.js";
|
|
6
6
|
import { FibWriteSpec, buildFib } from "./fib/write.js";
|
|
7
7
|
import { Plc, findLargestAtMost, parsePlc } from "./plc.js";
|
|
8
8
|
import { layoutMetadataToSummaryInformation } from "./metadata.js";
|
|
@@ -21,7 +21,7 @@ import { SectionProperties, applySectionSprms, readSectionProperties } from "./p
|
|
|
21
21
|
import { buildPlcfSed, buildSepx, encodeSectionGrpprl } from "./prop/sep-write.js";
|
|
22
22
|
import { ResolvedStyleFormatting, STI_USER_DEFINED, STK, Style, StyleSheet, buildStshForStyles, headingLevelFromIstd, parseStsh, resolveStyleFormatting } from "./style/stsh.js";
|
|
23
23
|
import { buildFontTable, parseFontTable } from "./style/fonts.js";
|
|
24
|
-
import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "./numbering-
|
|
24
|
+
import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "./numbering-DayjnHXY.js";
|
|
25
25
|
import { DocContent, DocStreams, ParagraphEntry, readDocContent, readDocStreams } from "./read.js";
|
|
26
26
|
import { WriteDocContentOptions, writeDocContent } from "./write.js";
|
|
27
|
-
export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, DRAWN_OBJECT, DocContent, DocFormatError, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, ResolvedStyleFormatting, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, tableStreamName, writeDocContent };
|
|
27
|
+
export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, CharacterProperties, ChpxFkp, ChpxRunToWrite, DRAWN_OBJECT, DocContent, DocFormatError, DocStreams, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, Fib, FibWriteSpec, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, NumberingDefinition, NumberingDefinitions, NumberingLevel, PARAGRAPH_MARK, PapxFkp, PapxLookup, PapxParagraphToWrite, PapxRecord, ParagraphEntry, ParagraphProperties, Piece, PieceTable, Plc, Prl, PropertyBinTable, ResolvedStyleFormatting, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, SectionProperties, Sprm, Style, StyleSheet, TextRange, WORD_DOCUMENT_STREAM, WriteDocContentOptions, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, tableStreamName, writeDocContent };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DocFormatError, DocUnsupportedError } from "./errors.js";
|
|
2
2
|
import { readInt16LE, readInt32LE, readUint16LE, readUint32LE, readUint8, slice } from "./bytes.js";
|
|
3
|
-
import { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET } from "./fib/offsets.js";
|
|
3
|
+
import { FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, LW_OFFSET } from "./fib/offsets.js";
|
|
4
4
|
import { SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM, isDocBytes } from "./detect.js";
|
|
5
5
|
import { findLargestAtMost, parsePlc } from "./plc.js";
|
|
6
|
-
import { parseFib, tableStreamName } from "./fib/fib.js";
|
|
6
|
+
import { parseFib, peekFibBaseFlags, tableStreamName } from "./fib/fib.js";
|
|
7
7
|
import { buildFib } from "./fib/write.js";
|
|
8
8
|
import { layoutMetadataToSummaryInformation } from "./metadata.js";
|
|
9
9
|
import { characterOffset, characterSize, parseClx } from "./text/piece-table.js";
|
|
@@ -24,4 +24,4 @@ import { buildFontTable, parseFontTable } from "./style/fonts.js";
|
|
|
24
24
|
import { NUMBER_FORMAT_BY_NFC, readNumberingDefinitions } from "./list/numbering.js";
|
|
25
25
|
import { readDocContent, readDocStreams } from "./read.js";
|
|
26
26
|
import { writeDocContent } from "./write.js";
|
|
27
|
-
export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, DRAWN_OBJECT, DocFormatError, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, PARAGRAPH_MARK, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, WORD_DOCUMENT_STREAM, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, tableStreamName, writeDocContent };
|
|
27
|
+
export { ANNOTATION_REFERENCE, CELL_MARK, COMPRESSED_CHARACTER_MAP, DRAWN_OBJECT, DocFormatError, DocUnsupportedError, FC_LCB_VALUE_INDEX, FIB_BASE_FLAG, FIB_BASE_SIZE, FIB_CB_RG_FC_LCB_OFFSET, FIB_CSLW_OFFSET, FIB_CSLW_REQUIRED, FIB_CSW_REQUIRED, FIB_FC_LCB_BLOB_OFFSET, FIB_LKEY_OFFSET, FIB_RG_LW_OFFSET, FIB_RG_LW_SIZE, FIB_RG_W_OFFSET, FIB_RG_W_SIZE, FIB_W_IDENT, FIELD_BEGIN, FIELD_END, FIELD_SEPARATOR, FKP_PAGE_SIZE, FOOTNOTE_REFERENCE, INLINE_PICTURE, LINE_BREAK, LW_OFFSET, NUMBER_FORMAT_BY_NFC, PARAGRAPH_MARK, PropertyBinTable, SECTION_MARK, SGC, STI_USER_DEFINED, STK, SUMMARY_INFORMATION_STREAM, SYMBOL_ANCHOR, WORD_DOCUMENT_STREAM, applyCharacterSprms, applyParagraphSprms, applySectionSprms, buildChpxPages, buildFib, buildFontTable, buildPapxPages, buildPlcfSed, buildPropertyBinTable, buildSepx, buildStshForStyles, buildTextClx, characterIstdFromGrpprl, characterOffset, characterSize, decodeSprm, encodeCharacterGrpprl, encodeParagraphGrpprl, encodeSectionGrpprl, endsParagraph, findLargestAtMost, firstFcOfPage, fitsAloneOnPapxPage, headingLevelFromIstd, isAnchorOnly, isDocBytes, layoutMetadataToSummaryInformation, operandSize, parseChpxFkp, parseClx, parseFib, parseFontTable, parsePapxFkp, parsePlc, parseStsh, peekFibBaseFlags, readDocContent, readDocStreams, readGrpprl, readInt16LE, readInt32LE, readNumberingDefinitions, readSectionProperties, readTextRange, readUint16LE, readUint32LE, readUint8, resolveStyleFormatting, slice, tableStreamName, writeDocContent };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as NumberingDefinitions } from "../numbering-
|
|
1
|
+
import { r as NumberingDefinitions } from "../numbering-DSpm9w6y.cjs";
|
|
2
2
|
import { ContentListMembership } from "document-schema.js";
|
|
3
3
|
//#region src/list/numbering-write.d.ts
|
|
4
4
|
/** One document's own numbering, gathered from every paragraph's list membership (the caller passes the already-flattened sequence, table cells included, so a list used only inside a table cell is still resolved). */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as NumberingDefinitions } from "../numbering-
|
|
1
|
+
import { r as NumberingDefinitions } from "../numbering-DayjnHXY.js";
|
|
2
2
|
import { ContentListMembership } from "document-schema.js";
|
|
3
3
|
//#region src/list/numbering-write.d.ts
|
|
4
4
|
/** One document's own numbering, gathered from every paragraph's list membership (the caller passes the already-flattened sequence, table cells included, so a list used only inside a table cell is still resolved). */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "../numbering-
|
|
1
|
+
import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "../numbering-DSpm9w6y.cjs";
|
|
2
2
|
export { NUMBER_FORMAT_BY_NFC, NumberingDefinition, NumberingDefinitions, NumberingLevel, readNumberingDefinitions };
|
package/dist/list/numbering.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "../numbering-
|
|
1
|
+
import { a as readNumberingDefinitions, i as NumberingLevel, n as NumberingDefinition, r as NumberingDefinitions, t as NUMBER_FORMAT_BY_NFC } from "../numbering-DayjnHXY.js";
|
|
2
2
|
export { NUMBER_FORMAT_BY_NFC, NumberingDefinition, NumberingDefinitions, NumberingLevel, readNumberingDefinitions };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as Fib } from "./fib-
|
|
1
|
+
import { t as Fib } from "./fib-DWFta19r.cjs";
|
|
2
2
|
//#region src/list/numbering.d.ts
|
|
3
3
|
/** MSONFC ([MS-OSHARED] 2.2.1.3), mapped to its own documented ST_NumberFormat equivalent -- the identical vocabulary ooxml.js's NumberingLevel.format carries verbatim from word/numbering.xml's own w:numFmt/@w:val. Every member through msonfcUCRus (0x3B) is a real numbered/lettered/ideograph format; 0x17 (msonfcBullet) is handled separately below since PlfLfo also treats it as the "no number sequence, but has bullets" case LVLF's own field text calls out by name. */
|
|
4
4
|
declare const NUMBER_FORMAT_BY_NFC: Readonly<Record<number, string>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as Fib } from "./fib-
|
|
1
|
+
import { t as Fib } from "./fib-DWFta19r.js";
|
|
2
2
|
//#region src/list/numbering.d.ts
|
|
3
3
|
/** MSONFC ([MS-OSHARED] 2.2.1.3), mapped to its own documented ST_NumberFormat equivalent -- the identical vocabulary ooxml.js's NumberingLevel.format carries verbatim from word/numbering.xml's own w:numFmt/@w:val. Every member through msonfcUCRus (0x3B) is a real numbered/lettered/ideograph format; 0x17 (msonfcBullet) is handled separately below since PlfLfo also treats it as the "no number sequence, but has bullets" case LVLF's own field text calls out by name. */
|
|
4
4
|
declare const NUMBER_FORMAT_BY_NFC: Readonly<Record<number, string>>;
|
package/dist/prop/sep.d.cts
CHANGED
package/dist/prop/sep.d.ts
CHANGED
package/dist/read.cjs
CHANGED
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
const require_errors = require("./errors.cjs");
|
|
3
3
|
const require_bytes = require("./bytes.cjs");
|
|
4
4
|
const require_detect = require("./detect.cjs");
|
|
5
|
+
const require_encryption = require("./encryption.cjs");
|
|
5
6
|
const require_fib_fib = require("./fib/fib.cjs");
|
|
6
7
|
const require_text_piece_table = require("./text/piece-table.cjs");
|
|
7
8
|
const require_text_characters = require("./text/characters.cjs");
|
|
@@ -28,24 +29,33 @@ const DEFAULT_MARGINS = {
|
|
|
28
29
|
bottomPt: 72,
|
|
29
30
|
leftPt: 72
|
|
30
31
|
};
|
|
31
|
-
function readDocStreams(bytes) {
|
|
32
|
+
function readDocStreams(bytes, password) {
|
|
32
33
|
const streams = (0, archive_codec.readCompoundFile)(bytes);
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
const
|
|
36
|
-
const wanted =
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
34
|
+
const wordDocumentStream = streams.find((stream) => stream.path === require_detect.WORD_DOCUMENT_STREAM);
|
|
35
|
+
if (wordDocumentStream === void 0) throw new require_errors.DocFormatError(`this compound file has no "${require_detect.WORD_DOCUMENT_STREAM}" stream, so it is not a Word Binary File (it holds: ${streams.map((stream) => stream.path).join(", ")})`);
|
|
36
|
+
const flags = require_fib_fib.peekFibBaseFlags(wordDocumentStream.bytes);
|
|
37
|
+
const wanted = flags.fWhichTblStm === 1 ? "1Table" : "0Table";
|
|
38
|
+
const tableStream = streams.find((stream) => stream.path === wanted);
|
|
39
|
+
if (tableStream === void 0) throw new require_errors.DocFormatError(`FibBase.fWhichTblStm selects the "${wanted}" stream, which this compound file does not contain`);
|
|
40
|
+
let wordDocument = wordDocumentStream.bytes;
|
|
41
|
+
let table = tableStream.bytes;
|
|
42
|
+
if (flags.fEncrypted) {
|
|
43
|
+
if (flags.fObfuscated) throw new require_errors.DocUnsupportedError("this document is XOR-obfuscated ([MS-DOC] 2.2.6.1); doc-codec cannot decrypt it, and reading its streams as plaintext would produce arbitrary text rather than the document's own");
|
|
44
|
+
const decrypted = require_encryption.decryptDocStreams(wordDocument, table, password);
|
|
45
|
+
wordDocument = decrypted.wordDocument;
|
|
46
|
+
table = decrypted.table;
|
|
47
|
+
}
|
|
48
|
+
const fib = require_fib_fib.parseFib(wordDocument);
|
|
39
49
|
const metadata = streams.find((stream) => stream.path === require_detect.SUMMARY_INFORMATION_STREAM);
|
|
40
50
|
return {
|
|
41
|
-
wordDocument
|
|
42
|
-
table
|
|
51
|
+
wordDocument,
|
|
52
|
+
table,
|
|
43
53
|
fib,
|
|
44
54
|
metadata: metadata?.bytes
|
|
45
55
|
};
|
|
46
56
|
}
|
|
47
|
-
function readDocContent(bytes) {
|
|
48
|
-
const { wordDocument, table, fib, metadata } = readDocStreams(bytes);
|
|
57
|
+
function readDocContent(bytes, password) {
|
|
58
|
+
const { wordDocument, table, fib, metadata } = readDocStreams(bytes, password);
|
|
49
59
|
const pieceTable = require_text_piece_table.parseClx(require_bytes.slice(table, fib.fcClx, fib.lcbClx, "Clx in the Table stream"));
|
|
50
60
|
const styles = fib.lcbStshf > 0 ? require_style_stsh.parseStsh(require_bytes.slice(table, fib.fcStshf, fib.lcbStshf, "STSH in the Table stream")) : void 0;
|
|
51
61
|
const chpxTable = new require_prop_fkp.PropertyBinTable(wordDocument, require_bytes.slice(table, fib.fcPlcfBteChpx, fib.lcbPlcfBteChpx, "PlcBteChpx in the Table stream"), "PlcBteChpx");
|
package/dist/read.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as Fib } from "./fib-
|
|
1
|
+
import { t as Fib } from "./fib-DWFta19r.cjs";
|
|
2
2
|
import { t as Prl } from "./sprm-DEyvdOo0.cjs";
|
|
3
3
|
import { t as ParagraphProperties } from "./pap-BT1p0uzu.cjs";
|
|
4
|
-
import { r as NumberingDefinitions } from "./numbering-
|
|
4
|
+
import { r as NumberingDefinitions } from "./numbering-DSpm9w6y.cjs";
|
|
5
5
|
import { ContentDocument, ContentParagraph } from "document-schema.js";
|
|
6
6
|
//#region src/read.d.ts
|
|
7
7
|
interface DocStreams {
|
|
@@ -11,12 +11,12 @@ interface DocStreams {
|
|
|
11
11
|
/** The raw "\x05SummaryInformation" stream bytes, or undefined when the container carries none -- a valid, spec-conformant Word Binary File need not carry document properties at all. */
|
|
12
12
|
readonly metadata: Uint8Array<ArrayBuffer> | undefined;
|
|
13
13
|
}
|
|
14
|
-
declare function readDocStreams(bytes: Uint8Array<ArrayBuffer
|
|
14
|
+
declare function readDocStreams(bytes: Uint8Array<ArrayBuffer>, password?: string): DocStreams;
|
|
15
15
|
/** readDocContent's own return type: a ContentDocument (kind 'wordprocessing') plus numbering -- the list-level formatting (glyph/format, level-text template, start-at value) PlfLst/PlfLfo carry, which ContentListMembership has nowhere to hold. Mirrors ooxml.js's own DocxDocument.numbering exactly in field name and NumberingDefinitions' own shape (see list/numbering.ts's top comment for why it sits outside the shared schema rather than inside ContentListMembership); unlike DocxDocument, DocContent stays a genuine ContentDocument subtype (an intersection, not a fresh shape) since readDocContent already had one return type to widen rather than two to reconcile. */
|
|
16
16
|
type DocContent = ContentDocument & {
|
|
17
17
|
readonly numbering: NumberingDefinitions;
|
|
18
18
|
};
|
|
19
|
-
declare function readDocContent(bytes: Uint8Array<ArrayBuffer
|
|
19
|
+
declare function readDocContent(bytes: Uint8Array<ArrayBuffer>, password?: string): DocContent;
|
|
20
20
|
/** One paragraph/cell/row-ending mark, still flat -- table/read.ts's assembleBlocks is what folds a run of these into a real ContentTable. `properties` and `grpprl` are carried alongside the already-built `paragraph` because table grouping needs sprmPFInTable/sprmPFTtp/sprmPItap (properties) and, on a row's own mark, its table-defining sgc-5 sprms (grpprl) -- neither of which survives onto a plain ContentParagraph. */
|
|
21
21
|
interface ParagraphEntry {
|
|
22
22
|
readonly paragraph: ContentParagraph;
|
package/dist/read.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as Fib } from "./fib-
|
|
1
|
+
import { t as Fib } from "./fib-DWFta19r.js";
|
|
2
2
|
import { t as Prl } from "./sprm-DEyvdOo0.js";
|
|
3
3
|
import { t as ParagraphProperties } from "./pap-DOFLMWcG.js";
|
|
4
|
-
import { r as NumberingDefinitions } from "./numbering-
|
|
4
|
+
import { r as NumberingDefinitions } from "./numbering-DayjnHXY.js";
|
|
5
5
|
import { ContentDocument, ContentParagraph } from "document-schema.js";
|
|
6
6
|
//#region src/read.d.ts
|
|
7
7
|
interface DocStreams {
|
|
@@ -11,12 +11,12 @@ interface DocStreams {
|
|
|
11
11
|
/** The raw "\x05SummaryInformation" stream bytes, or undefined when the container carries none -- a valid, spec-conformant Word Binary File need not carry document properties at all. */
|
|
12
12
|
readonly metadata: Uint8Array<ArrayBuffer> | undefined;
|
|
13
13
|
}
|
|
14
|
-
declare function readDocStreams(bytes: Uint8Array<ArrayBuffer
|
|
14
|
+
declare function readDocStreams(bytes: Uint8Array<ArrayBuffer>, password?: string): DocStreams;
|
|
15
15
|
/** readDocContent's own return type: a ContentDocument (kind 'wordprocessing') plus numbering -- the list-level formatting (glyph/format, level-text template, start-at value) PlfLst/PlfLfo carry, which ContentListMembership has nowhere to hold. Mirrors ooxml.js's own DocxDocument.numbering exactly in field name and NumberingDefinitions' own shape (see list/numbering.ts's top comment for why it sits outside the shared schema rather than inside ContentListMembership); unlike DocxDocument, DocContent stays a genuine ContentDocument subtype (an intersection, not a fresh shape) since readDocContent already had one return type to widen rather than two to reconcile. */
|
|
16
16
|
type DocContent = ContentDocument & {
|
|
17
17
|
readonly numbering: NumberingDefinitions;
|
|
18
18
|
};
|
|
19
|
-
declare function readDocContent(bytes: Uint8Array<ArrayBuffer
|
|
19
|
+
declare function readDocContent(bytes: Uint8Array<ArrayBuffer>, password?: string): DocContent;
|
|
20
20
|
/** One paragraph/cell/row-ending mark, still flat -- table/read.ts's assembleBlocks is what folds a run of these into a real ContentTable. `properties` and `grpprl` are carried alongside the already-built `paragraph` because table grouping needs sprmPFInTable/sprmPFTtp/sprmPItap (properties) and, on a row's own mark, its table-defining sgc-5 sprms (grpprl) -- neither of which survives onto a plain ContentParagraph. */
|
|
21
21
|
interface ParagraphEntry {
|
|
22
22
|
readonly paragraph: ContentParagraph;
|
package/dist/read.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { DocFormatError } from "./errors.js";
|
|
1
|
+
import { DocFormatError, DocUnsupportedError } from "./errors.js";
|
|
2
2
|
import { slice } from "./bytes.js";
|
|
3
3
|
import { SUMMARY_INFORMATION_STREAM, WORD_DOCUMENT_STREAM } from "./detect.js";
|
|
4
|
-
import {
|
|
4
|
+
import { decryptDocStreams } from "./encryption.js";
|
|
5
|
+
import { parseFib, peekFibBaseFlags } from "./fib/fib.js";
|
|
5
6
|
import { parseClx } from "./text/piece-table.js";
|
|
6
7
|
import { readTextRange } from "./text/characters.js";
|
|
7
8
|
import { endsParagraph, isAnchorOnly } from "./text/special.js";
|
|
@@ -27,24 +28,33 @@ const DEFAULT_MARGINS = {
|
|
|
27
28
|
bottomPt: 72,
|
|
28
29
|
leftPt: 72
|
|
29
30
|
};
|
|
30
|
-
function readDocStreams(bytes) {
|
|
31
|
+
function readDocStreams(bytes, password) {
|
|
31
32
|
const streams = readCompoundFile(bytes);
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
const
|
|
35
|
-
const wanted =
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
33
|
+
const wordDocumentStream = streams.find((stream) => stream.path === WORD_DOCUMENT_STREAM);
|
|
34
|
+
if (wordDocumentStream === void 0) throw new DocFormatError(`this compound file has no "${WORD_DOCUMENT_STREAM}" stream, so it is not a Word Binary File (it holds: ${streams.map((stream) => stream.path).join(", ")})`);
|
|
35
|
+
const flags = peekFibBaseFlags(wordDocumentStream.bytes);
|
|
36
|
+
const wanted = flags.fWhichTblStm === 1 ? "1Table" : "0Table";
|
|
37
|
+
const tableStream = streams.find((stream) => stream.path === wanted);
|
|
38
|
+
if (tableStream === void 0) throw new DocFormatError(`FibBase.fWhichTblStm selects the "${wanted}" stream, which this compound file does not contain`);
|
|
39
|
+
let wordDocument = wordDocumentStream.bytes;
|
|
40
|
+
let table = tableStream.bytes;
|
|
41
|
+
if (flags.fEncrypted) {
|
|
42
|
+
if (flags.fObfuscated) throw new DocUnsupportedError("this document is XOR-obfuscated ([MS-DOC] 2.2.6.1); doc-codec cannot decrypt it, and reading its streams as plaintext would produce arbitrary text rather than the document's own");
|
|
43
|
+
const decrypted = decryptDocStreams(wordDocument, table, password);
|
|
44
|
+
wordDocument = decrypted.wordDocument;
|
|
45
|
+
table = decrypted.table;
|
|
46
|
+
}
|
|
47
|
+
const fib = parseFib(wordDocument);
|
|
38
48
|
const metadata = streams.find((stream) => stream.path === SUMMARY_INFORMATION_STREAM);
|
|
39
49
|
return {
|
|
40
|
-
wordDocument
|
|
41
|
-
table
|
|
50
|
+
wordDocument,
|
|
51
|
+
table,
|
|
42
52
|
fib,
|
|
43
53
|
metadata: metadata?.bytes
|
|
44
54
|
};
|
|
45
55
|
}
|
|
46
|
-
function readDocContent(bytes) {
|
|
47
|
-
const { wordDocument, table, fib, metadata } = readDocStreams(bytes);
|
|
56
|
+
function readDocContent(bytes, password) {
|
|
57
|
+
const { wordDocument, table, fib, metadata } = readDocStreams(bytes, password);
|
|
48
58
|
const pieceTable = parseClx(slice(table, fib.fcClx, fib.lcbClx, "Clx in the Table stream"));
|
|
49
59
|
const styles = fib.lcbStshf > 0 ? parseStsh(slice(table, fib.fcStshf, fib.lcbStshf, "STSH in the Table stream")) : void 0;
|
|
50
60
|
const chpxTable = new PropertyBinTable(wordDocument, slice(table, fib.fcPlcfBteChpx, fib.lcbPlcfBteChpx, "PlcBteChpx in the Table stream"), "PlcBteChpx");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doc-codec",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "A hand-written reader for the Word Binary File Format ([MS-DOC], .doc) against the shared document-schema.js content pivot: FIB parsing, piece-table text reconstruction, and CHPX/PAPX formatting exceptions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"license": "MIT",
|
|
79
79
|
"packageManager": "pnpm@11.6.0",
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"archive-codec": "1.
|
|
81
|
+
"archive-codec": "1.8.0",
|
|
82
82
|
"document-schema.js": "7.3.1"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|