@naturalcycles/nodejs-lib 15.108.0 → 15.110.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/dist/csv/csvWriter.js +1 -1
- package/dist/fs/fs2.d.ts +2 -2
- package/dist/validation/ajv/from-data/generateJsonSchemaFromData.js +1 -1
- package/dist/validation/ajv/jSchema.js +4 -6
- package/dist/zip/index.d.ts +3 -0
- package/dist/zip/index.js +3 -0
- package/dist/zip/zip2.d.ts +37 -0
- package/dist/zip/zip2.js +51 -0
- package/dist/zip/zipInternal.d.ts +41 -0
- package/dist/zip/zipInternal.js +88 -0
- package/dist/zip/zipReader.d.ts +142 -0
- package/dist/zip/zipReader.js +475 -0
- package/dist/zip/zipWriter.d.ts +168 -0
- package/dist/zip/zipWriter.js +579 -0
- package/package.json +2 -2
- package/src/csv/csvWriter.ts +1 -1
- package/src/jwt/jwt.service.ts +1 -0
- package/src/stream/transform/worker/workerClassProxy.js +1 -0
- package/src/validation/ajv/from-data/generateJsonSchemaFromData.ts +1 -1
- package/src/validation/ajv/jSchema.ts +4 -6
- package/src/zip/index.ts +3 -0
- package/src/zip/zip2.ts +56 -0
- package/src/zip/zipInternal.ts +111 -0
- package/src/zip/zipReader.ts +620 -0
- package/src/zip/zipWriter.ts +792 -0
package/src/zip/zip2.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs'
|
|
1
2
|
import { promisify } from 'node:util'
|
|
2
3
|
import type { ZlibOptions, ZstdOptions } from 'node:zlib'
|
|
3
4
|
import zlib from 'node:zlib'
|
|
4
5
|
import type { Integer } from '@naturalcycles/js-lib/types'
|
|
6
|
+
import { openZip } from './zipReader.js'
|
|
7
|
+
import type { ZipEntry } from './zipReader.js'
|
|
8
|
+
import { createZipBuffer, zipPaths, ZipWriter } from './zipWriter.js'
|
|
9
|
+
import type { ZipFileEntry, ZipPathsOptions } from './zipWriter.js'
|
|
5
10
|
|
|
6
11
|
const deflateAsync = promisify(zlib.deflate.bind(zlib))
|
|
7
12
|
const inflateAsync = promisify(zlib.inflate.bind(zlib))
|
|
@@ -158,6 +163,57 @@ class Zip2 {
|
|
|
158
163
|
isGzipBuffer(input: Buffer): boolean {
|
|
159
164
|
return input[0] === 0x1f && input[1] === 0x8b
|
|
160
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Open a zip file and extract all of its entries into `destDir`, streaming each
|
|
169
|
+
* file to disk. Directories are created as needed.
|
|
170
|
+
*
|
|
171
|
+
* Path-traversal attempts (absolute paths, `..` segments, or paths escaping
|
|
172
|
+
* `destDir`) are rejected.
|
|
173
|
+
*
|
|
174
|
+
* Returns the list of extracted entries.
|
|
175
|
+
*/
|
|
176
|
+
async extractZipFileToDirectory(zipFilePath: string, destDir: string): Promise<ZipEntry[]> {
|
|
177
|
+
const zip = await openZip(zipFilePath)
|
|
178
|
+
try {
|
|
179
|
+
return await zip.extractAll(destDir)
|
|
180
|
+
} finally {
|
|
181
|
+
await zip.close()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a zip archive on disk from a list of files and/or directories.
|
|
187
|
+
*/
|
|
188
|
+
async zipPaths(paths: string[], outputZipFilePath: string, opt?: ZipPathsOptions): Promise<void> {
|
|
189
|
+
return await zipPaths(paths, outputZipFilePath, opt)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create a zip archive on disk, returning a {@link ZipWriter} that streams to it.
|
|
194
|
+
*
|
|
195
|
+
* Add entries with {@link ZipWriter.addFile}/{@link ZipWriter.addBuffer}/etc.,
|
|
196
|
+
* then call {@link ZipWriter.finalize} (or use `await using`):
|
|
197
|
+
*
|
|
198
|
+
* ```ts
|
|
199
|
+
* const zip = createZip('archive.zip')
|
|
200
|
+
* await zip.addFile('./photo.jpg')
|
|
201
|
+
* await zip.addBuffer(Buffer.from('{"a":1}'), 'data.json')
|
|
202
|
+
* await zip.finalize()
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
createZip(filePath: string): ZipWriter {
|
|
206
|
+
return new ZipWriter(createWriteStream(filePath))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build a zip archive entirely in memory and return it as a Buffer.
|
|
211
|
+
*
|
|
212
|
+
* For large archives or streamed inputs prefer {@link createZip} / {@link ZipWriter}.
|
|
213
|
+
*/
|
|
214
|
+
async createZipBuffer(entries: ZipFileEntry[]): Promise<Buffer> {
|
|
215
|
+
return await createZipBuffer(entries)
|
|
216
|
+
}
|
|
161
217
|
}
|
|
162
218
|
|
|
163
219
|
export const zip2 = new Zip2()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Shared low-level helpers and constants for the zip reader and writer.
|
|
4
|
+
|
|
5
|
+
These are the parts of the ZIP file format that both reading (`zipReader.ts`) and
|
|
6
|
+
writing (`zipWriter.ts`) need in common: record signatures and fixed sizes, 64-bit
|
|
7
|
+
integer read/write helpers, DOS date/time conversion (both directions) and
|
|
8
|
+
entry-name normalization/validation.
|
|
9
|
+
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { UnixTimestamp } from '@naturalcycles/js-lib/types'
|
|
13
|
+
|
|
14
|
+
// oxlint-disable no-bitwise -- the ZIP format packs DOS date/time into bit fields
|
|
15
|
+
|
|
16
|
+
// Compression methods.
|
|
17
|
+
export const STORED = 0
|
|
18
|
+
export const DEFLATE = 8
|
|
19
|
+
|
|
20
|
+
// Record signatures (little-endian uint32) and fixed sizes (in bytes).
|
|
21
|
+
export const LOCAL_FILE_HEADER_SIG = 0x04034b50
|
|
22
|
+
export const DATA_DESCRIPTOR_SIG = 0x08074b50
|
|
23
|
+
export const CDFH_SIG = 0x02014b50
|
|
24
|
+
export const CDFH_SIZE = 46
|
|
25
|
+
export const EOCDR_SIG = 0x06054b50
|
|
26
|
+
export const EOCDR_SIZE = 22
|
|
27
|
+
export const ZIP64_EOCDL_SIG = 0x07064b50
|
|
28
|
+
export const ZIP64_EOCDL_SIZE = 20
|
|
29
|
+
export const ZIP64_EOCDR_SIG = 0x06064b50
|
|
30
|
+
export const ZIP64_EOCDR_SIZE = 56
|
|
31
|
+
|
|
32
|
+
/** The .zip comment and per-entry name/comment length fields are all 16-bit. */
|
|
33
|
+
export const MAX_COMMENT_SIZE = 0xffff
|
|
34
|
+
|
|
35
|
+
const MAX_SAFE_INTEGER_BIG = BigInt(Number.MAX_SAFE_INTEGER)
|
|
36
|
+
|
|
37
|
+
/** Read a 64-bit little-endian unsigned integer, rejecting values above `Number.MAX_SAFE_INTEGER`. */
|
|
38
|
+
export function readUInt64LE(buf: Buffer, offset: number): number {
|
|
39
|
+
const value = buf.readBigUInt64LE(offset)
|
|
40
|
+
if (value > MAX_SAFE_INTEGER_BIG) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'zip file too large: 64-bit values above Number.MAX_SAFE_INTEGER are not supported',
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
return Number(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Write a 64-bit little-endian unsigned integer. */
|
|
49
|
+
export function writeUInt64LE(buf: Buffer, value: number, offset: number): void {
|
|
50
|
+
buf.writeBigUInt64LE(BigInt(value), offset)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Decode a packed DOS date + time pair into a {@link UnixTimestamp} (seconds).
|
|
55
|
+
* The DOS fields are local-time, so they are interpreted in the local timezone.
|
|
56
|
+
* Used when no Info-ZIP extended timestamp is present.
|
|
57
|
+
*/
|
|
58
|
+
export function dosDateTimeToUnix(date: number, time: number): UnixTimestamp {
|
|
59
|
+
const day = date & 0x1f // 1-31
|
|
60
|
+
const month = ((date >> 5) & 0x0f) - 1 // 1-12 -> 0-11
|
|
61
|
+
const year = ((date >> 9) & 0x7f) + 1980 // 0-127 -> 1980-2107
|
|
62
|
+
const second = (time & 0x1f) * 2 // 0-29 -> 0-58
|
|
63
|
+
const minute = (time >> 5) & 0x3f // 0-59
|
|
64
|
+
const hour = (time >> 11) & 0x1f // 0-23
|
|
65
|
+
return Math.floor(
|
|
66
|
+
new Date(year, month, day, hour, minute, second).getTime() / 1000,
|
|
67
|
+
) as UnixTimestamp
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const MIN_DOS_DATE = new Date(1980, 0, 1)
|
|
71
|
+
const MAX_DOS_DATE = new Date(2107, 11, 31, 23, 59, 58)
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Encode a {@link UnixTimestamp} (seconds) into the packed DOS date + time pair stored
|
|
75
|
+
* in local file and central directory headers. The DOS fields are local-time, so the
|
|
76
|
+
* timestamp is rendered in the local timezone. Out-of-range dates are clamped to 1980-2107.
|
|
77
|
+
*/
|
|
78
|
+
export function unixToDosDateTime(ts: UnixTimestamp): { date: number; time: number } {
|
|
79
|
+
const jsDate = new Date(ts * 1000)
|
|
80
|
+
const d = jsDate < MIN_DOS_DATE ? MIN_DOS_DATE : jsDate > MAX_DOS_DATE ? MAX_DOS_DATE : jsDate
|
|
81
|
+
|
|
82
|
+
const date =
|
|
83
|
+
(d.getDate() & 0x1f) | // 1-31
|
|
84
|
+
(((d.getMonth() + 1) & 0x0f) << 5) | // 1-12
|
|
85
|
+
(((d.getFullYear() - 1980) & 0x7f) << 9) // 1980-2107
|
|
86
|
+
|
|
87
|
+
const time =
|
|
88
|
+
Math.floor(d.getSeconds() / 2) | // 0-29
|
|
89
|
+
((d.getMinutes() & 0x3f) << 5) | // 0-59
|
|
90
|
+
((d.getHours() & 0x1f) << 11) // 0-23
|
|
91
|
+
|
|
92
|
+
return { date, time }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Normalize Windows-style separators, like yauzl in non-strict mode. */
|
|
96
|
+
export function normalizeZipEntryName(name: string): string {
|
|
97
|
+
return name.replaceAll('\\', '/')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Reject entry names that would escape the extraction directory: absolute paths
|
|
102
|
+
* (drive letters or a leading `/`) and any `..` path segment.
|
|
103
|
+
*/
|
|
104
|
+
export function assertSafeZipEntryName(name: string): void {
|
|
105
|
+
if (/^[a-z]:/i.test(name) || name.startsWith('/')) {
|
|
106
|
+
throw new Error(`absolute path in zip entry: ${name}`)
|
|
107
|
+
}
|
|
108
|
+
if (name.split('/').includes('..')) {
|
|
109
|
+
throw new Error(`invalid relative path in zip entry: ${name}`)
|
|
110
|
+
}
|
|
111
|
+
}
|