archive-codec 1.1.2 → 1.2.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 +20 -18
- package/dist/cfb/read.cjs +1 -1
- package/dist/cfb/read.js +1 -1
- package/dist/zip/detect.d.cts +1 -1
- package/dist/zip/detect.d.ts +1 -1
- package/dist/zip/walk.d.cts +1 -1
- package/dist/zip/walk.d.ts +1 -1
- package/package.json +1 -10
package/README.md
CHANGED
|
@@ -32,33 +32,33 @@ To run a single test file, pass its path to vitest directly, e.g. `pnpm exec vit
|
|
|
32
32
|
Every module is importable by package-relative path as well as through the barrel — `tsdown` builds one dist file per src module (`root: 'src'`, the same layout ooxml.js ships), and `package.json`'s `./*` exports wildcard maps each subpath onto it:
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
|
-
import { readCompoundFile } from
|
|
36
|
-
import { walkArchive } from
|
|
35
|
+
import { readCompoundFile } from "archive-codec/cfb/read";
|
|
36
|
+
import { walkArchive } from "archive-codec/zip/walk";
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
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.
|
|
40
40
|
|
|
41
|
-
| Module
|
|
42
|
-
|
|
43
|
-
| `zip/container`
|
|
44
|
-
| `zip/detect`
|
|
45
|
-
| `zip/walk`
|
|
46
|
-
| `cfb/detect`
|
|
47
|
-
| `cfb/read`
|
|
48
|
-
| `cfb/ole-package` | `readOlePackage` (OLE Package stream unwrapping), `OlePackage`, `OlePackageFormatError`
|
|
41
|
+
| Module | Exports |
|
|
42
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `zip/container` | `zipPackage` (ordered-entries ZIP write with stored-uncompressed support), `unzipPackage`, `ZipEntry` |
|
|
44
|
+
| `zip/detect` | `detectArchiveFormat` (`'zip' \| 'cfb' \| 'unknown'`), `isZipArchive`, `ArchiveFormat` |
|
|
45
|
+
| `zip/walk` | `walkArchive` (recursive ZIP-in-ZIP walking), `ArchiveWalkEntry`, `ArchiveWalkLimitError`, `MAX_WALK_DEPTH`, `MAX_WALK_TOTAL_BYTES`, `WalkArchiveOptions` |
|
|
46
|
+
| `cfb/detect` | `isCompoundFile` (the `D0 CF 11 E0 …` magic-byte check) |
|
|
47
|
+
| `cfb/read` | `readCompoundFile` (bounded [MS-CFB] stream extraction), `CompoundFileStream`, `CompoundFileFormatError`, `MAX_CFB_TOTAL_STREAM_BYTES`, `ReadCompoundFileOptions` |
|
|
48
|
+
| `cfb/ole-package` | `readOlePackage` (OLE Package stream unwrapping), `OlePackage`, `OlePackageFormatError` |
|
|
49
49
|
|
|
50
50
|
### Recursive walking
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
|
-
import { walkArchive } from
|
|
53
|
+
import { walkArchive } from "archive-codec";
|
|
54
54
|
|
|
55
55
|
// Every entry of every nested ZIP, flattened. Throws ArchiveWalkLimitError if
|
|
56
56
|
// the walk exceeds the depth cap or the cumulative decompressed-bytes budget.
|
|
57
57
|
for (const entry of walkArchive(docxBytes)) {
|
|
58
|
-
entry.path;
|
|
58
|
+
entry.path; // e.g. 'xl/workbook.xml', the path within its own archive
|
|
59
59
|
entry.ancestors; // e.g. ['word/embeddings/oleObject1.xlsx'] -- the nested
|
|
60
|
-
|
|
61
|
-
entry.bytes;
|
|
60
|
+
// ZIP entries descended through to reach this one
|
|
61
|
+
entry.bytes; // decompressed content
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -67,18 +67,20 @@ Both guards throw rather than truncate: an input outside the contract must fail
|
|
|
67
67
|
### Compound files
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
|
-
import { readCompoundFile, readOlePackage } from
|
|
70
|
+
import { readCompoundFile, readOlePackage } from "archive-codec";
|
|
71
71
|
|
|
72
72
|
// Every stream of a classic OLE compound file, with its storage path.
|
|
73
73
|
// Throws CompoundFileFormatError on any structural nonconformance.
|
|
74
74
|
for (const stream of readCompoundFile(oleBinBytes)) {
|
|
75
|
-
stream.path;
|
|
75
|
+
stream.path; // e.g. 'Package' -- root-level, or 'ObjectStorage/Package'
|
|
76
76
|
stream.bytes; // the stream's content
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// The OLE packaging a Word/PowerPoint embed wraps the real file in before
|
|
80
80
|
// storing it as the 'Package' stream: label, paths, and the file's bytes.
|
|
81
|
-
const packageStream = readCompoundFile(oleBinBytes).find(
|
|
81
|
+
const packageStream = readCompoundFile(oleBinBytes).find(
|
|
82
|
+
(s) => s.path === "Package",
|
|
83
|
+
);
|
|
82
84
|
if (packageStream !== undefined) {
|
|
83
85
|
readOlePackage(packageStream.bytes).fileBytes; // often a ZIP for a modern embed
|
|
84
86
|
}
|
|
@@ -88,7 +90,7 @@ Reading is bounded the same way walking is: chain cycles and out-of-range sector
|
|
|
88
90
|
|
|
89
91
|
### ZIP container
|
|
90
92
|
|
|
91
|
-
`zipPackage` takes an
|
|
93
|
+
`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.
|
|
92
94
|
|
|
93
95
|
## Conventions
|
|
94
96
|
|
package/dist/cfb/read.cjs
CHANGED
|
@@ -113,7 +113,7 @@ function readCompoundFile(bytes, options = {}) {
|
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
115
|
const root = entries[0];
|
|
116
|
-
if (root
|
|
116
|
+
if (root?.objectType !== OBJECT_TYPE_ROOT) throw new CompoundFileFormatError("the first directory entry is not the root storage entry (object type 5), as [MS-CFB] 2.6.1 requires");
|
|
117
117
|
const miniStream = chainBytes(root.startSector).subarray(0, root.size);
|
|
118
118
|
const miniSectorCount = Math.floor(miniStream.length / miniSectorSize);
|
|
119
119
|
const miniFatBytes = chainBytes(firstMiniFatSector);
|
package/dist/cfb/read.js
CHANGED
|
@@ -112,7 +112,7 @@ function readCompoundFile(bytes, options = {}) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
const root = entries[0];
|
|
115
|
-
if (root
|
|
115
|
+
if (root?.objectType !== OBJECT_TYPE_ROOT) throw new CompoundFileFormatError("the first directory entry is not the root storage entry (object type 5), as [MS-CFB] 2.6.1 requires");
|
|
116
116
|
const miniStream = chainBytes(root.startSector).subarray(0, root.size);
|
|
117
117
|
const miniSectorCount = Math.floor(miniStream.length / miniSectorSize);
|
|
118
118
|
const miniFatBytes = chainBytes(firstMiniFatSector);
|
package/dist/zip/detect.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/zip/detect.d.ts
|
|
2
|
-
type ArchiveFormat =
|
|
2
|
+
type ArchiveFormat = "zip" | "cfb" | "unknown";
|
|
3
3
|
declare function isZipArchive(bytes: Uint8Array): boolean;
|
|
4
4
|
declare function detectArchiveFormat(bytes: Uint8Array): ArchiveFormat;
|
|
5
5
|
//#endregion
|
package/dist/zip/detect.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/zip/detect.d.ts
|
|
2
|
-
type ArchiveFormat =
|
|
2
|
+
type ArchiveFormat = "zip" | "cfb" | "unknown";
|
|
3
3
|
declare function isZipArchive(bytes: Uint8Array): boolean;
|
|
4
4
|
declare function detectArchiveFormat(bytes: Uint8Array): ArchiveFormat;
|
|
5
5
|
//#endregion
|
package/dist/zip/walk.d.cts
CHANGED
|
@@ -6,7 +6,7 @@ interface ArchiveWalkEntry {
|
|
|
6
6
|
readonly ancestors: readonly string[];
|
|
7
7
|
readonly bytes: Uint8Array<ArrayBuffer>;
|
|
8
8
|
}
|
|
9
|
-
type ArchiveWalkLimit =
|
|
9
|
+
type ArchiveWalkLimit = "depth" | "total-bytes";
|
|
10
10
|
declare class ArchiveWalkLimitError extends Error {
|
|
11
11
|
readonly limit: ArchiveWalkLimit;
|
|
12
12
|
constructor(limit: ArchiveWalkLimit, message: string);
|
package/dist/zip/walk.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ interface ArchiveWalkEntry {
|
|
|
6
6
|
readonly ancestors: readonly string[];
|
|
7
7
|
readonly bytes: Uint8Array<ArrayBuffer>;
|
|
8
8
|
}
|
|
9
|
-
type ArchiveWalkLimit =
|
|
9
|
+
type ArchiveWalkLimit = "depth" | "total-bytes";
|
|
10
10
|
declare class ArchiveWalkLimitError extends Error {
|
|
11
11
|
readonly limit: ArchiveWalkLimit;
|
|
12
12
|
constructor(limit: ArchiveWalkLimit, message: string);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archive-codec",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "ZIP-in-ZIP recursive walking with depth and cumulative decompressed-size guards, plus bounded classic OLE compound-file ([MS-CFB]) reading - zero document-format knowledge, the archive and container utility package for the documents.js family.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -72,22 +72,13 @@
|
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
74
74
|
"@cloudflare/vitest-pool-workers": "^0.20.1",
|
|
75
|
-
"@commitlint/cli": "^21.2.1",
|
|
76
|
-
"@commitlint/config-conventional": "^21.2.0",
|
|
77
|
-
"@eslint/js": "^10.0.1",
|
|
78
|
-
"@semantic-release/changelog": "^7.0.0",
|
|
79
|
-
"@semantic-release/git": "^11.0.1",
|
|
80
75
|
"@types/node": "^26.1.2",
|
|
81
76
|
"eslint": "^10.8.0",
|
|
82
|
-
"globals": "^17.8.0",
|
|
83
77
|
"husky": "^9.1.7",
|
|
84
|
-
"lint-staged": "^17.2.0",
|
|
85
78
|
"publint": "^0.3.21",
|
|
86
|
-
"semantic-release": "^25.0.8",
|
|
87
79
|
"tsdown": "^0.22.13",
|
|
88
80
|
"turbo": "^2.10.8",
|
|
89
81
|
"typescript": "^6.0.3",
|
|
90
|
-
"typescript-eslint": "^8.65.0",
|
|
91
82
|
"vitest": "^4.1.10"
|
|
92
83
|
},
|
|
93
84
|
"lint-staged": {
|