@patrickdk77/office-defuse 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ import type { ByteSink } from './types';
2
+ /** What the central directory says of an entry whose compressed bytes are copied unchanged. */
3
+ export interface CopiedEntry {
4
+ name: string;
5
+ /** 0 stored, 8 deflate. */
6
+ method: number;
7
+ crc: number;
8
+ compressedSize: number;
9
+ size: number;
10
+ dosTime?: number;
11
+ dosDate?: number;
12
+ }
13
+ /**
14
+ * Writes a ZIP archive to an append-only sink. Local headers carry the CRC and sizes, so no entry needs a data
15
+ * descriptor. ZIP64 records appear only where a size, an offset or the entry count needs them.
16
+ */
17
+ export declare class ZipWriter {
18
+ private readonly sink;
19
+ private readonly options;
20
+ private offset;
21
+ private readonly central;
22
+ private readonly names;
23
+ /** `forceZip64` writes ZIP64 records for every entry and the end of the archive, as a test of readers. */
24
+ constructor(sink: ByteSink, options?: {
25
+ forceZip64?: boolean;
26
+ });
27
+ private put;
28
+ /** Adds an entry from bytes in memory, deflated unless `compress` is false. A name ending in "/" is a directory. */
29
+ add(name: string, data: Uint8Array, options?: {
30
+ compress?: boolean;
31
+ dosTime?: number;
32
+ dosDate?: number;
33
+ }): Promise<void>;
34
+ /** Adds an entry whose compressed bytes come from `chunks` unchanged, such as an entry of the archive being rewritten. */
35
+ copy(info: CopiedEntry, chunks: AsyncIterable<Uint8Array>): Promise<void>;
36
+ /** Writes the central directory and the end records. The caller closes the sink. */
37
+ finish(): Promise<void>;
38
+ }
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ZipWriter = void 0;
37
+ const zlib = __importStar(require("node:zlib"));
38
+ const crc32_1 = require("./crc32");
39
+ const MAX16 = 0xffff;
40
+ const MAX32 = 0xffffffff;
41
+ /** 1980-01-01 00:00, the earliest date a ZIP header holds, so new entries do not depend on the clock. */
42
+ const DOS_DATE = (0 << 9) | (1 << 5) | 1;
43
+ class Builder {
44
+ parts = [];
45
+ u16(v) {
46
+ this.parts.push(v & 0xff, (v >>> 8) & 0xff);
47
+ return this;
48
+ }
49
+ u32(v) {
50
+ return this.u16(v & 0xffff).u16(Math.floor(v / 0x10000) & 0xffff);
51
+ }
52
+ u64(v) {
53
+ return this.u32(v % 0x100000000).u32(Math.floor(v / 0x100000000));
54
+ }
55
+ bytes(b) {
56
+ for (const x of b)
57
+ this.parts.push(x);
58
+ return this;
59
+ }
60
+ build() {
61
+ return Uint8Array.from(this.parts);
62
+ }
63
+ }
64
+ /**
65
+ * Writes a ZIP archive to an append-only sink. Local headers carry the CRC and sizes, so no entry needs a data
66
+ * descriptor. ZIP64 records appear only where a size, an offset or the entry count needs them.
67
+ */
68
+ class ZipWriter {
69
+ sink;
70
+ options;
71
+ offset = 0;
72
+ central = [];
73
+ names = new Set();
74
+ /** `forceZip64` writes ZIP64 records for every entry and the end of the archive, as a test of readers. */
75
+ constructor(sink, options = {}) {
76
+ this.sink = sink;
77
+ this.options = options;
78
+ }
79
+ async put(b) {
80
+ await this.sink.write(b);
81
+ this.offset += b.length;
82
+ }
83
+ /** Adds an entry from bytes in memory, deflated unless `compress` is false. A name ending in "/" is a directory. */
84
+ async add(name, data, options = {}) {
85
+ const compress = (options.compress ?? true) && data.length > 0;
86
+ const body = compress ? zlib.deflateRawSync(data) : data;
87
+ const info = { name, method: compress ? 8 : 0, crc: (0, crc32_1.crc32)(data), compressedSize: body.length, size: data.length, dosTime: options.dosTime, dosDate: options.dosDate };
88
+ await this.copy(info, (async function* () {
89
+ yield body;
90
+ })());
91
+ }
92
+ /** Adds an entry whose compressed bytes come from `chunks` unchanged, such as an entry of the archive being rewritten. */
93
+ async copy(info, chunks) {
94
+ if (this.names.has(info.name))
95
+ throw new Error(`Entry ${info.name} is already in the archive`);
96
+ this.names.add(info.name);
97
+ const name = Buffer.from(info.name, 'utf8');
98
+ const flags = /^[\x20-\x7e]*$/.test(info.name) ? 0 : 0x0800;
99
+ const big = this.options.forceZip64 === true || info.size >= MAX32 || info.compressedSize >= MAX32;
100
+ const localOffset = this.offset;
101
+ const time = info.dosTime ?? 0;
102
+ const date = info.dosDate ?? DOS_DATE;
103
+ const local = new Builder()
104
+ .u32(0x04034b50)
105
+ .u16(big ? 45 : 20)
106
+ .u16(flags)
107
+ .u16(info.method)
108
+ .u16(time)
109
+ .u16(date)
110
+ .u32(info.crc)
111
+ .u32(big ? MAX32 : info.compressedSize)
112
+ .u32(big ? MAX32 : info.size)
113
+ .u16(name.length)
114
+ .u16(big ? 20 : 0)
115
+ .bytes(name);
116
+ if (big)
117
+ local.u16(0x0001).u16(16).u64(info.size).u64(info.compressedSize);
118
+ await this.put(local.build());
119
+ let written = 0;
120
+ for await (const c of chunks) {
121
+ written += c.length;
122
+ await this.put(c);
123
+ }
124
+ if (written !== info.compressedSize)
125
+ throw new Error(`Entry ${info.name} has ${written} compressed bytes, not ${info.compressedSize}`);
126
+ const farOffset = this.options.forceZip64 === true || localOffset >= MAX32;
127
+ const extra = new Builder();
128
+ if (big)
129
+ extra.u64(info.size).u64(info.compressedSize);
130
+ if (farOffset)
131
+ extra.u64(localOffset);
132
+ const extraBytes = extra.build();
133
+ const version = big || farOffset ? 45 : 20;
134
+ const central = new Builder()
135
+ .u32(0x02014b50)
136
+ .u16(version)
137
+ .u16(version)
138
+ .u16(flags)
139
+ .u16(info.method)
140
+ .u16(time)
141
+ .u16(date)
142
+ .u32(info.crc)
143
+ .u32(big ? MAX32 : info.compressedSize)
144
+ .u32(big ? MAX32 : info.size)
145
+ .u16(name.length)
146
+ .u16(extraBytes.length ? extraBytes.length + 4 : 0)
147
+ .u16(0)
148
+ .u16(0)
149
+ .u16(0)
150
+ .u32(0)
151
+ .u32(farOffset ? MAX32 : localOffset)
152
+ .bytes(name);
153
+ if (extraBytes.length)
154
+ central.u16(0x0001).u16(extraBytes.length).bytes(extraBytes);
155
+ this.central.push(central.build());
156
+ }
157
+ /** Writes the central directory and the end records. The caller closes the sink. */
158
+ async finish() {
159
+ const dirOffset = this.offset;
160
+ for (const c of this.central)
161
+ await this.put(c);
162
+ const dirSize = this.offset - dirOffset;
163
+ const count = this.central.length;
164
+ const big = this.options.forceZip64 === true || count >= MAX16 || dirOffset >= MAX32 || dirSize >= MAX32;
165
+ if (big) {
166
+ const end64 = this.offset;
167
+ await this.put(new Builder().u32(0x06064b50).u64(44).u16(45).u16(45).u32(0).u32(0).u64(count).u64(count).u64(dirSize).u64(dirOffset).build());
168
+ await this.put(new Builder().u32(0x07064b50).u32(0).u64(end64).u32(1).build());
169
+ }
170
+ await this.put(new Builder()
171
+ .u32(0x06054b50)
172
+ .u16(0)
173
+ .u16(0)
174
+ .u16(big ? MAX16 : count)
175
+ .u16(big ? MAX16 : count)
176
+ .u32(big ? MAX32 : dirSize)
177
+ .u32(big ? MAX32 : dirOffset)
178
+ .u16(0)
179
+ .build());
180
+ }
181
+ }
182
+ exports.ZipWriter = ZipWriter;
package/dist/zip.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { type ByteSource, OfficeDetail as D } from './types';
2
+ export interface ZipLimits {
3
+ entries: number;
4
+ decompressedBytes: number;
5
+ totalDecompressedBytes: number;
6
+ }
7
+ export interface ZipEntry {
8
+ /** Position in the central directory. */
9
+ index: number;
10
+ name: string;
11
+ /** A directory entry, whose name ends in "/". */
12
+ directory: boolean;
13
+ flags: number;
14
+ /** 0 stored, 8 deflate. */
15
+ method: number;
16
+ dosTime: number;
17
+ dosDate: number;
18
+ crc: number;
19
+ compressedSize: number;
20
+ size: number;
21
+ localOffset: number;
22
+ dataOffset: number;
23
+ /** Where the entry ends, after any data descriptor. */
24
+ end: number;
25
+ /** The local header has an extra field. ODF wants none on the mimetype entry. */
26
+ localExtra: boolean;
27
+ }
28
+ /** Something the reader saw that it does not refuse. Each is a strip finding, since the rewrite drops the bytes. */
29
+ export interface ZipNote {
30
+ detail: D.LeadingBytes | D.HiddenData | D.TrailingBytes;
31
+ bytes: number;
32
+ }
33
+ /** The key two names collide on: percent-decoded, NFC and lowercase, without a directory's slash. */
34
+ export declare function nameKey(name: string): string;
35
+ /**
36
+ * A ZIP archive read from its central directory, after every local header was checked against it. Reading an entry
37
+ * inflates it in chunks and checks its size and CRC.
38
+ */
39
+ export declare class ZipArchive {
40
+ private readonly reader;
41
+ readonly entries: ZipEntry[];
42
+ readonly notes: ZipNote[];
43
+ private readonly deadline;
44
+ private readonly byKey;
45
+ private constructor();
46
+ /** The entry with this name, compared as entry names collide: ignoring case. */
47
+ get(name: string): ZipEntry | undefined;
48
+ static open(source: ByteSource, limits: ZipLimits, deadline?: number): Promise<ZipArchive>;
49
+ /** Checks a local header and any data descriptor against the central directory, and fills in where the entry's data and the entry end. */
50
+ private static checkLocal;
51
+ /** The compressed bytes of an entry, as stored. */
52
+ raw(e: ZipEntry): AsyncGenerator<Uint8Array>;
53
+ /** The entry's content in chunks. Its size, its CRC and, for deflate, the compressed bytes consumed must all agree with the central directory. */
54
+ read(e: ZipEntry): AsyncGenerator<Uint8Array>;
55
+ }