@patrickdk77/archive-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.
- package/LICENSE +21 -0
- package/README.md +345 -2
- package/dist/aes7z.d.ts +16 -0
- package/dist/aes7z.js +131 -0
- package/dist/alone.d.ts +21 -0
- package/dist/alone.js +65 -0
- package/dist/bcj2.d.ts +21 -0
- package/dist/bcj2.js +140 -0
- package/dist/bzip2.d.ts +21 -0
- package/dist/bzip2.js +688 -0
- package/dist/cab.d.ts +63 -0
- package/dist/cab.js +493 -0
- package/dist/checks.d.ts +23 -0
- package/dist/checks.js +123 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +364 -0
- package/dist/codecs.d.ts +39 -0
- package/dist/codecs.js +124 -0
- package/dist/coders.d.ts +58 -0
- package/dist/coders.js +392 -0
- package/dist/crc32.d.ts +8 -0
- package/dist/crc32.js +59 -0
- package/dist/encoder.d.ts +44 -0
- package/dist/encoder.js +114 -0
- package/dist/engine.d.ts +44 -0
- package/dist/engine.js +753 -0
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +98 -0
- package/dist/filters.d.ts +34 -0
- package/dist/filters.js +385 -0
- package/dist/findings.d.ts +26 -0
- package/dist/findings.js +176 -0
- package/dist/formats.d.ts +14 -0
- package/dist/formats.js +97 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +50 -0
- package/dist/index.mjs +3 -0
- package/dist/inflate64.d.ts +6 -0
- package/dist/inflate64.js +388 -0
- package/dist/io.d.ts +78 -0
- package/dist/io.js +379 -0
- package/dist/lzma-formats.d.ts +75 -0
- package/dist/lzma-formats.js +373 -0
- package/dist/lzma-sniff.d.ts +20 -0
- package/dist/lzma-sniff.js +73 -0
- package/dist/lzma-writers.d.ts +35 -0
- package/dist/lzma-writers.js +276 -0
- package/dist/lzma1/LICENSE +201 -0
- package/dist/lzma1/NOTICE +36 -0
- package/dist/lzma1/crc32.d.ts +4 -0
- package/dist/lzma1/crc32.js +19 -0
- package/dist/lzma1/hash234.d.ts +22 -0
- package/dist/lzma1/hash234.js +75 -0
- package/dist/lzma1/hc4.d.ts +30 -0
- package/dist/lzma1/hc4.js +142 -0
- package/dist/lzma1/header.d.ts +32 -0
- package/dist/lzma1/header.js +73 -0
- package/dist/lzma1/lz-decoder.d.ts +41 -0
- package/dist/lzma1/lz-decoder.js +128 -0
- package/dist/lzma1/lz-encoder.d.ts +87 -0
- package/dist/lzma1/lz-encoder.js +166 -0
- package/dist/lzma1/lzma-coder.d.ts +107 -0
- package/dist/lzma1/lzma-coder.js +162 -0
- package/dist/lzma1/lzma-decoder.d.ts +36 -0
- package/dist/lzma1/lzma-decoder.js +484 -0
- package/dist/lzma1/lzma-encoder-fast.d.ts +13 -0
- package/dist/lzma1/lzma-encoder-fast.js +114 -0
- package/dist/lzma1/lzma-encoder.d.ts +105 -0
- package/dist/lzma1/lzma-encoder.js +497 -0
- package/dist/lzma1/lzma2-decoder.d.ts +85 -0
- package/dist/lzma1/lzma2-decoder.js +249 -0
- package/dist/lzma1/range-coder.d.ts +18 -0
- package/dist/lzma1/range-coder.js +23 -0
- package/dist/lzma1/range-decoder.d.ts +41 -0
- package/dist/lzma1/range-decoder.js +78 -0
- package/dist/lzma1/range-encoder.d.ts +53 -0
- package/dist/lzma1/range-encoder.js +269 -0
- package/dist/lzma1-stream.d.ts +60 -0
- package/dist/lzma1-stream.js +202 -0
- package/dist/model.d.ts +141 -0
- package/dist/model.js +16 -0
- package/dist/names.d.ts +13 -0
- package/dist/names.js +66 -0
- package/dist/pack.d.ts +72 -0
- package/dist/pack.js +514 -0
- package/dist/plugins.d.ts +13 -0
- package/dist/plugins.js +55 -0
- package/dist/rules.d.ts +25 -0
- package/dist/rules.js +403 -0
- package/dist/score.d.ts +17 -0
- package/dist/score.js +58 -0
- package/dist/seven.d.ts +75 -0
- package/dist/seven.js +492 -0
- package/dist/sevenzip.d.ts +133 -0
- package/dist/sevenzip.js +529 -0
- package/dist/single.d.ts +103 -0
- package/dist/single.js +453 -0
- package/dist/sniff.d.ts +20 -0
- package/dist/sniff.js +224 -0
- package/dist/stream-meter.d.ts +39 -0
- package/dist/stream-meter.js +54 -0
- package/dist/tar.d.ts +102 -0
- package/dist/tar.js +765 -0
- package/dist/types.d.ts +284 -0
- package/dist/types.js +133 -0
- package/dist/unpack.d.ts +67 -0
- package/dist/unpack.js +194 -0
- package/dist/xz.d.ts +25 -0
- package/dist/xz.js +333 -0
- package/dist/zip-lzma.d.ts +15 -0
- package/dist/zip-lzma.js +137 -0
- package/dist/zip-writer.d.ts +41 -0
- package/dist/zip-writer.js +263 -0
- package/dist/zip.d.ts +99 -0
- package/dist/zip.js +901 -0
- package/dist/zipcrypt.d.ts +47 -0
- package/dist/zipcrypt.js +233 -0
- package/dist/zstd.d.ts +25 -0
- package/dist/zstd.js +160 -0
- package/package.json +94 -4
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/** High-level kind of a finding. */
|
|
2
|
+
export declare enum ArchiveCategory {
|
|
3
|
+
Corrupted = "CORRUPTED",
|
|
4
|
+
Structure = "STRUCTURE",
|
|
5
|
+
Name = "NAME",
|
|
6
|
+
Link = "LINK",
|
|
7
|
+
Special = "SPECIAL",
|
|
8
|
+
Permissions = "PERMISSIONS",
|
|
9
|
+
Encrypted = "ENCRYPTED",
|
|
10
|
+
Compression = "COMPRESSION",
|
|
11
|
+
Entry = "ENTRY",
|
|
12
|
+
Metadata = "METADATA",
|
|
13
|
+
Limit = "LIMIT",
|
|
14
|
+
Processing = "PROCESSING"
|
|
15
|
+
}
|
|
16
|
+
/** Exact variant of a finding. */
|
|
17
|
+
export declare enum ArchiveDetail {
|
|
18
|
+
Unparseable = "UNPARSEABLE",
|
|
19
|
+
Truncated = "TRUNCATED",
|
|
20
|
+
DataError = "DATA_ERROR",
|
|
21
|
+
EntryData = "ENTRY_DATA",
|
|
22
|
+
CheckFailed = "CHECK_FAILED",
|
|
23
|
+
LeadingBytes = "LEADING_BYTES",
|
|
24
|
+
TrailingBytes = "TRAILING_BYTES",
|
|
25
|
+
HiddenData = "HIDDEN_DATA",
|
|
26
|
+
Polyglot = "POLYGLOT",
|
|
27
|
+
SeveralEndRecords = "SEVERAL_END_RECORDS",
|
|
28
|
+
HeaderMismatch = "HEADER_MISMATCH",
|
|
29
|
+
OffsetShift = "OFFSET_SHIFT",
|
|
30
|
+
Overlap = "OVERLAP",
|
|
31
|
+
ExtraField = "EXTRA_FIELD",
|
|
32
|
+
NonFileData = "NON_FILE_DATA",
|
|
33
|
+
MultipleStreams = "MULTIPLE_STREAMS",
|
|
34
|
+
MalformedHeader = "MALFORMED_HEADER",
|
|
35
|
+
MultiVolume = "MULTI_VOLUME",
|
|
36
|
+
StreamPadding = "STREAM_PADDING",
|
|
37
|
+
BadPadding = "BAD_PADDING",
|
|
38
|
+
IndexMismatch = "INDEX_MISMATCH",
|
|
39
|
+
SizeMismatch = "SIZE_MISMATCH",
|
|
40
|
+
NoCheck = "NO_CHECK",
|
|
41
|
+
UnknownCheck = "UNKNOWN_CHECK",
|
|
42
|
+
UnknownProperty = "UNKNOWN_PROPERTY",
|
|
43
|
+
EmptyStartHeader = "EMPTY_START_HEADER",
|
|
44
|
+
Traversal = "TRAVERSAL",
|
|
45
|
+
Absolute = "ABSOLUTE",
|
|
46
|
+
DriveLetter = "DRIVE_LETTER",
|
|
47
|
+
Backslash = "BACKSLASH",
|
|
48
|
+
EmptySegment = "EMPTY_SEGMENT",
|
|
49
|
+
ControlCharacter = "CONTROL_CHARACTER",
|
|
50
|
+
WindowsReserved = "WINDOWS_RESERVED",
|
|
51
|
+
Encoding = "ENCODING",
|
|
52
|
+
UnicodePath = "UNICODE_PATH",
|
|
53
|
+
Empty = "EMPTY",
|
|
54
|
+
TooLong = "TOO_LONG",
|
|
55
|
+
Duplicate = "DUPLICATE",
|
|
56
|
+
CaseCollision = "CASE_COLLISION",
|
|
57
|
+
NormalizationCollision = "NORMALIZATION_COLLISION",
|
|
58
|
+
FileDirectoryClash = "FILE_DIRECTORY_CLASH",
|
|
59
|
+
ShortNameCollision = "SHORT_NAME_COLLISION",
|
|
60
|
+
Symlink = "SYMLINK",
|
|
61
|
+
Hardlink = "HARDLINK",
|
|
62
|
+
Outside = "OUTSIDE",
|
|
63
|
+
Dangling = "DANGLING",
|
|
64
|
+
ThroughLink = "THROUGH_LINK",
|
|
65
|
+
Device = "DEVICE",
|
|
66
|
+
Fifo = "FIFO",
|
|
67
|
+
Socket = "SOCKET",
|
|
68
|
+
UnknownType = "UNKNOWN_TYPE",
|
|
69
|
+
AntiItem = "ANTI_ITEM",
|
|
70
|
+
Setuid = "SETUID",
|
|
71
|
+
Setgid = "SETGID",
|
|
72
|
+
Sticky = "STICKY",
|
|
73
|
+
Capability = "CAPABILITY",
|
|
74
|
+
ZipCrypto = "ZIPCRYPTO",
|
|
75
|
+
Aes128 = "AES_128",
|
|
76
|
+
Aes192 = "AES_192",
|
|
77
|
+
Aes256 = "AES_256",
|
|
78
|
+
Decrypted = "DECRYPTED",
|
|
79
|
+
EmptyPassword = "EMPTY_PASSWORD",
|
|
80
|
+
PasswordRequired = "PASSWORD_REQUIRED",
|
|
81
|
+
Unsupported = "UNSUPPORTED",
|
|
82
|
+
Header = "HEADER",
|
|
83
|
+
UnsupportedMethod = "UNSUPPORTED_METHOD",
|
|
84
|
+
UnsupportedFeature = "UNSUPPORTED_FEATURE",
|
|
85
|
+
UnusualMethod = "UNUSUAL_METHOD",
|
|
86
|
+
Unavailable = "UNAVAILABLE",
|
|
87
|
+
NoPlugin = "NO_PLUGIN",
|
|
88
|
+
TypeMismatch = "TYPE_MISMATCH",
|
|
89
|
+
PluginPassed = "PLUGIN_PASSED",
|
|
90
|
+
PluginScrubbed = "PLUGIN_SCRUBBED",
|
|
91
|
+
PluginFailed = "PLUGIN_FAILED",
|
|
92
|
+
PluginRemoved = "PLUGIN_REMOVED",
|
|
93
|
+
Comment = "COMMENT",
|
|
94
|
+
Stripped = "STRIPPED",
|
|
95
|
+
FileSize = "FILE_SIZE",
|
|
96
|
+
EntryCount = "ENTRY_COUNT",
|
|
97
|
+
DecompressedSize = "DECOMPRESSED_SIZE",
|
|
98
|
+
TotalDecompressedSize = "TOTAL_DECOMPRESSED_SIZE",
|
|
99
|
+
Ratio = "RATIO",
|
|
100
|
+
DictionarySize = "DICTIONARY_SIZE",
|
|
101
|
+
NestingDepth = "NESTING_DEPTH",
|
|
102
|
+
Time = "TIME",
|
|
103
|
+
MemoryFallback = "MEMORY_FALLBACK",
|
|
104
|
+
VerificationFailed = "VERIFICATION_FAILED",
|
|
105
|
+
ContentRemoved = "CONTENT_REMOVED"
|
|
106
|
+
}
|
|
107
|
+
export type ArchiveFindingAction = 'reject' | 'strip' | 'info';
|
|
108
|
+
/**
|
|
109
|
+
* A finding as any package of the defuse family reports it. Category and detail are plain strings, since the string
|
|
110
|
+
* enums of two packages never match each other.
|
|
111
|
+
*/
|
|
112
|
+
export interface DefuseFinding {
|
|
113
|
+
category: string;
|
|
114
|
+
detail: string;
|
|
115
|
+
/** Plain sentence, safe to show the uploader. */
|
|
116
|
+
description: string;
|
|
117
|
+
/** Default action after any caller override. */
|
|
118
|
+
action: 'reject' | 'strip' | 'info';
|
|
119
|
+
/** Where in the file, such as the name of an entry. */
|
|
120
|
+
location?: string;
|
|
121
|
+
/** Extra values: the entry name, sizes, counts, the plugin name. */
|
|
122
|
+
data?: Record<string, string | number>;
|
|
123
|
+
/**
|
|
124
|
+
* Set when the finding comes from inside a contained file, such as a PDF inside the archive: the file's name, with
|
|
125
|
+
* " > " between nesting levels. `location` then starts with where that file sits.
|
|
126
|
+
*/
|
|
127
|
+
attachment?: string;
|
|
128
|
+
/** How much the finding adds to a score, set by the package that made it. */
|
|
129
|
+
weight?: number;
|
|
130
|
+
}
|
|
131
|
+
/** A finding archive-defuse makes itself. */
|
|
132
|
+
export interface ArchiveFinding extends DefuseFinding {
|
|
133
|
+
category: ArchiveCategory;
|
|
134
|
+
detail: ArchiveDetail;
|
|
135
|
+
}
|
|
136
|
+
export declare enum ArchiveRisk {
|
|
137
|
+
None = "NONE",
|
|
138
|
+
Low = "LOW",
|
|
139
|
+
Medium = "MEDIUM",
|
|
140
|
+
High = "HIGH",
|
|
141
|
+
Critical = "CRITICAL",
|
|
142
|
+
/** The file could not be inspected: unparseable, an entry no password opens, or a limit stopped the run. */
|
|
143
|
+
Unknown = "UNKNOWN"
|
|
144
|
+
}
|
|
145
|
+
/** The formats archive-defuse reads and writes. */
|
|
146
|
+
export type ArchiveFormat = 'zip' | 'tar' | 'gzip' | 'bzip2' | 'zstd' | 'brotli' | 'cab' | '7z' | 'xz' | 'lzma';
|
|
147
|
+
export interface ArchiveInspection {
|
|
148
|
+
status: 'clean' | 'strippable' | 'rejected';
|
|
149
|
+
/** 0 to 100, null when risk is UNKNOWN. */
|
|
150
|
+
score: number | null;
|
|
151
|
+
risk: ArchiveRisk;
|
|
152
|
+
findings: DefuseFinding[];
|
|
153
|
+
/** The format the content was read as. Absent when it matched none. */
|
|
154
|
+
format?: ArchiveFormat;
|
|
155
|
+
/** Entries in the archive, or 1 for a compressed file. Absent when the archive could not be opened. */
|
|
156
|
+
entries?: number;
|
|
157
|
+
}
|
|
158
|
+
export interface ArchiveDisarmResult {
|
|
159
|
+
status: 'clean' | 'defused' | 'rejected';
|
|
160
|
+
/** The uploaded file and its score. */
|
|
161
|
+
before: ArchiveInspection;
|
|
162
|
+
/** The output and its remaining score. Absent when rejected. */
|
|
163
|
+
after?: ArchiveInspection;
|
|
164
|
+
/** Findings whose content was removed. */
|
|
165
|
+
removed: DefuseFinding[];
|
|
166
|
+
}
|
|
167
|
+
/** Random-access input. A buffer, a local file and an S3 ranged GetObject all fit. */
|
|
168
|
+
export interface ByteSource {
|
|
169
|
+
size(): Promise<number>;
|
|
170
|
+
read(offset: number, length: number): Promise<Uint8Array>;
|
|
171
|
+
close?(): Promise<void>;
|
|
172
|
+
}
|
|
173
|
+
/** Append-only output. Never seeks. */
|
|
174
|
+
export interface ByteSink {
|
|
175
|
+
write(chunk: Uint8Array): Promise<void>;
|
|
176
|
+
close(): Promise<void>;
|
|
177
|
+
/** Called instead of close() when copying to the sink fails, so a partial output is not taken for a whole one. */
|
|
178
|
+
abort?(error: unknown): Promise<void>;
|
|
179
|
+
}
|
|
180
|
+
export interface ArchiveLimits {
|
|
181
|
+
/** Bytes in the upload. Not enforced unless set. */
|
|
182
|
+
fileSize?: number;
|
|
183
|
+
/** Entries in the archive. Default 10,000. */
|
|
184
|
+
entries?: number;
|
|
185
|
+
/** Decompressed size of any one entry, declared or actual, in bytes. Default 512 MiB. */
|
|
186
|
+
decompressedBytes?: number;
|
|
187
|
+
/** Decompressed sizes of the entries read in one run added together, declared or actual, in bytes. Default 2 GiB. */
|
|
188
|
+
totalDecompressedBytes?: number;
|
|
189
|
+
/**
|
|
190
|
+
* Decompressed bytes per compressed byte of one entry, checked once the entry passes 1 MiB decompressed. Default
|
|
191
|
+
* 1,000.
|
|
192
|
+
*/
|
|
193
|
+
compressionRatio?: number;
|
|
194
|
+
/**
|
|
195
|
+
* The deepest an archive may sit in the upload. The upload is at depth 0 and a file inside it at 1, and files of
|
|
196
|
+
* every format in between count. Default 10, since an archive can hold itself.
|
|
197
|
+
*/
|
|
198
|
+
nestingDepth?: number;
|
|
199
|
+
/**
|
|
200
|
+
* The dictionary memory LZMA decoding may need, in bytes: one LZMA or LZMA2 stream's dictionary, or those of all the
|
|
201
|
+
* coders of one 7z folder together with their probability tables. A dictionary larger than the declared decoded size
|
|
202
|
+
* counts at that size. Default 256 MiB.
|
|
203
|
+
*/
|
|
204
|
+
dictionaryBytes?: number;
|
|
205
|
+
/** Milliseconds for the whole run. Not enforced unless set. */
|
|
206
|
+
timeMs?: number;
|
|
207
|
+
}
|
|
208
|
+
/** What a contained file plugin learns of the run, with nothing format-specific, so any package of the family can make one. */
|
|
209
|
+
export interface PluginContext {
|
|
210
|
+
/** The depth of the file itself: 0 for the upload, 1 for a file inside it, and so on, whatever the formats. */
|
|
211
|
+
depth: number;
|
|
212
|
+
/** When a time limit is set, the Date.now() value the whole run must finish by. Nested runs share it. */
|
|
213
|
+
deadline?: number;
|
|
214
|
+
}
|
|
215
|
+
export interface ContainedFile {
|
|
216
|
+
name?: string;
|
|
217
|
+
/** The MIME type the container declares for the file. Archives declare none. */
|
|
218
|
+
declaredType?: string;
|
|
219
|
+
/** From the first bytes. */
|
|
220
|
+
sniffedType?: string;
|
|
221
|
+
/** Decoded size. */
|
|
222
|
+
size: number;
|
|
223
|
+
location: string;
|
|
224
|
+
/** The depth of the file itself: 0 for the upload, 1 for a file inside it, and so on, whatever the formats. */
|
|
225
|
+
depth: number;
|
|
226
|
+
/** Decoded and decrypted bytes. */
|
|
227
|
+
source: ByteSource;
|
|
228
|
+
}
|
|
229
|
+
/** What a file plugin did, plus anything it found inside the file. */
|
|
230
|
+
export interface ContainedFileResult {
|
|
231
|
+
result: 'scrubbed' | 'passed' | 'removed';
|
|
232
|
+
/** Findings inside the file as it arrived. They go into the report of the file that contains it. */
|
|
233
|
+
findings?: DefuseFinding[];
|
|
234
|
+
/** Findings inside the bytes the plugin kept. They go into the report of the output. */
|
|
235
|
+
outputFindings?: DefuseFinding[];
|
|
236
|
+
}
|
|
237
|
+
export interface ContainedFilePlugin {
|
|
238
|
+
kind: 'file';
|
|
239
|
+
name: string;
|
|
240
|
+
accepts(file: ContainedFile): boolean | Promise<boolean>;
|
|
241
|
+
/** Writes the bytes to keep. Returning 'removed' drops the file after all. */
|
|
242
|
+
process(file: ContainedFile, sink: ByteSink, context: PluginContext): Promise<'scrubbed' | 'passed' | 'removed' | ContainedFileResult>;
|
|
243
|
+
}
|
|
244
|
+
export interface ArchiveActionOverride {
|
|
245
|
+
category: ArchiveCategory;
|
|
246
|
+
detail?: ArchiveDetail;
|
|
247
|
+
action: ArchiveFindingAction;
|
|
248
|
+
}
|
|
249
|
+
export interface ArchiveScoreWeight {
|
|
250
|
+
category: ArchiveCategory;
|
|
251
|
+
detail?: ArchiveDetail;
|
|
252
|
+
weight: number;
|
|
253
|
+
}
|
|
254
|
+
/** Lower bound of each band. */
|
|
255
|
+
export interface ArchiveScoreBands {
|
|
256
|
+
low: number;
|
|
257
|
+
medium: number;
|
|
258
|
+
high: number;
|
|
259
|
+
critical: number;
|
|
260
|
+
}
|
|
261
|
+
export interface ArchiveOptions {
|
|
262
|
+
/**
|
|
263
|
+
* Passwords for encrypted entries. Each is tried in order on each encrypted entry, and the empty password after
|
|
264
|
+
* them.
|
|
265
|
+
*/
|
|
266
|
+
password?: string | string[];
|
|
267
|
+
/** Limits left out take the defaults listed on ArchiveLimits. */
|
|
268
|
+
limits?: ArchiveLimits;
|
|
269
|
+
/** Default false. The rewrite drops comments, owner names and ids, timestamps and extended attributes. */
|
|
270
|
+
stripMetadata?: boolean;
|
|
271
|
+
actionOverrides?: ArchiveActionOverride[];
|
|
272
|
+
scoreWeights?: ArchiveScoreWeight[];
|
|
273
|
+
scoreBands?: ArchiveScoreBands;
|
|
274
|
+
/** Every file entry goes to these in order. The first that accepts it decides. */
|
|
275
|
+
filePlugins?: ContainedFilePlugin[];
|
|
276
|
+
/** Directory for temporary files. Defaults to os.tmpdir(). */
|
|
277
|
+
tempDir?: string;
|
|
278
|
+
/**
|
|
279
|
+
* Default 8 MiB. disarmArchiveSource keeps its copy of the upload in memory up to this size and in a temporary file
|
|
280
|
+
* above it. Each entry handed to the plugins above it goes through a temporary file, and the copies plugins keep
|
|
281
|
+
* share this much memory.
|
|
282
|
+
*/
|
|
283
|
+
memoryThreshold?: number;
|
|
284
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ArchiveRisk = exports.ArchiveDetail = exports.ArchiveCategory = void 0;
|
|
4
|
+
/** High-level kind of a finding. */
|
|
5
|
+
var ArchiveCategory;
|
|
6
|
+
(function (ArchiveCategory) {
|
|
7
|
+
ArchiveCategory["Corrupted"] = "CORRUPTED";
|
|
8
|
+
ArchiveCategory["Structure"] = "STRUCTURE";
|
|
9
|
+
ArchiveCategory["Name"] = "NAME";
|
|
10
|
+
ArchiveCategory["Link"] = "LINK";
|
|
11
|
+
ArchiveCategory["Special"] = "SPECIAL";
|
|
12
|
+
ArchiveCategory["Permissions"] = "PERMISSIONS";
|
|
13
|
+
ArchiveCategory["Encrypted"] = "ENCRYPTED";
|
|
14
|
+
ArchiveCategory["Compression"] = "COMPRESSION";
|
|
15
|
+
ArchiveCategory["Entry"] = "ENTRY";
|
|
16
|
+
ArchiveCategory["Metadata"] = "METADATA";
|
|
17
|
+
ArchiveCategory["Limit"] = "LIMIT";
|
|
18
|
+
ArchiveCategory["Processing"] = "PROCESSING";
|
|
19
|
+
})(ArchiveCategory || (exports.ArchiveCategory = ArchiveCategory = {}));
|
|
20
|
+
/** Exact variant of a finding. */
|
|
21
|
+
var ArchiveDetail;
|
|
22
|
+
(function (ArchiveDetail) {
|
|
23
|
+
// CORRUPTED
|
|
24
|
+
ArchiveDetail["Unparseable"] = "UNPARSEABLE";
|
|
25
|
+
ArchiveDetail["Truncated"] = "TRUNCATED";
|
|
26
|
+
ArchiveDetail["DataError"] = "DATA_ERROR";
|
|
27
|
+
ArchiveDetail["EntryData"] = "ENTRY_DATA";
|
|
28
|
+
ArchiveDetail["CheckFailed"] = "CHECK_FAILED";
|
|
29
|
+
// STRUCTURE
|
|
30
|
+
ArchiveDetail["LeadingBytes"] = "LEADING_BYTES";
|
|
31
|
+
ArchiveDetail["TrailingBytes"] = "TRAILING_BYTES";
|
|
32
|
+
ArchiveDetail["HiddenData"] = "HIDDEN_DATA";
|
|
33
|
+
ArchiveDetail["Polyglot"] = "POLYGLOT";
|
|
34
|
+
ArchiveDetail["SeveralEndRecords"] = "SEVERAL_END_RECORDS";
|
|
35
|
+
ArchiveDetail["HeaderMismatch"] = "HEADER_MISMATCH";
|
|
36
|
+
ArchiveDetail["OffsetShift"] = "OFFSET_SHIFT";
|
|
37
|
+
ArchiveDetail["Overlap"] = "OVERLAP";
|
|
38
|
+
ArchiveDetail["ExtraField"] = "EXTRA_FIELD";
|
|
39
|
+
ArchiveDetail["NonFileData"] = "NON_FILE_DATA";
|
|
40
|
+
ArchiveDetail["MultipleStreams"] = "MULTIPLE_STREAMS";
|
|
41
|
+
ArchiveDetail["MalformedHeader"] = "MALFORMED_HEADER";
|
|
42
|
+
ArchiveDetail["MultiVolume"] = "MULTI_VOLUME";
|
|
43
|
+
ArchiveDetail["StreamPadding"] = "STREAM_PADDING";
|
|
44
|
+
ArchiveDetail["BadPadding"] = "BAD_PADDING";
|
|
45
|
+
ArchiveDetail["IndexMismatch"] = "INDEX_MISMATCH";
|
|
46
|
+
ArchiveDetail["SizeMismatch"] = "SIZE_MISMATCH";
|
|
47
|
+
ArchiveDetail["NoCheck"] = "NO_CHECK";
|
|
48
|
+
ArchiveDetail["UnknownCheck"] = "UNKNOWN_CHECK";
|
|
49
|
+
ArchiveDetail["UnknownProperty"] = "UNKNOWN_PROPERTY";
|
|
50
|
+
ArchiveDetail["EmptyStartHeader"] = "EMPTY_START_HEADER";
|
|
51
|
+
// NAME
|
|
52
|
+
ArchiveDetail["Traversal"] = "TRAVERSAL";
|
|
53
|
+
ArchiveDetail["Absolute"] = "ABSOLUTE";
|
|
54
|
+
ArchiveDetail["DriveLetter"] = "DRIVE_LETTER";
|
|
55
|
+
ArchiveDetail["Backslash"] = "BACKSLASH";
|
|
56
|
+
ArchiveDetail["EmptySegment"] = "EMPTY_SEGMENT";
|
|
57
|
+
ArchiveDetail["ControlCharacter"] = "CONTROL_CHARACTER";
|
|
58
|
+
ArchiveDetail["WindowsReserved"] = "WINDOWS_RESERVED";
|
|
59
|
+
ArchiveDetail["Encoding"] = "ENCODING";
|
|
60
|
+
ArchiveDetail["UnicodePath"] = "UNICODE_PATH";
|
|
61
|
+
ArchiveDetail["Empty"] = "EMPTY";
|
|
62
|
+
ArchiveDetail["TooLong"] = "TOO_LONG";
|
|
63
|
+
ArchiveDetail["Duplicate"] = "DUPLICATE";
|
|
64
|
+
ArchiveDetail["CaseCollision"] = "CASE_COLLISION";
|
|
65
|
+
ArchiveDetail["NormalizationCollision"] = "NORMALIZATION_COLLISION";
|
|
66
|
+
ArchiveDetail["FileDirectoryClash"] = "FILE_DIRECTORY_CLASH";
|
|
67
|
+
ArchiveDetail["ShortNameCollision"] = "SHORT_NAME_COLLISION";
|
|
68
|
+
// LINK
|
|
69
|
+
ArchiveDetail["Symlink"] = "SYMLINK";
|
|
70
|
+
ArchiveDetail["Hardlink"] = "HARDLINK";
|
|
71
|
+
ArchiveDetail["Outside"] = "OUTSIDE";
|
|
72
|
+
ArchiveDetail["Dangling"] = "DANGLING";
|
|
73
|
+
ArchiveDetail["ThroughLink"] = "THROUGH_LINK";
|
|
74
|
+
// SPECIAL
|
|
75
|
+
ArchiveDetail["Device"] = "DEVICE";
|
|
76
|
+
ArchiveDetail["Fifo"] = "FIFO";
|
|
77
|
+
ArchiveDetail["Socket"] = "SOCKET";
|
|
78
|
+
ArchiveDetail["UnknownType"] = "UNKNOWN_TYPE";
|
|
79
|
+
ArchiveDetail["AntiItem"] = "ANTI_ITEM";
|
|
80
|
+
// PERMISSIONS
|
|
81
|
+
ArchiveDetail["Setuid"] = "SETUID";
|
|
82
|
+
ArchiveDetail["Setgid"] = "SETGID";
|
|
83
|
+
ArchiveDetail["Sticky"] = "STICKY";
|
|
84
|
+
ArchiveDetail["Capability"] = "CAPABILITY";
|
|
85
|
+
// ENCRYPTED
|
|
86
|
+
ArchiveDetail["ZipCrypto"] = "ZIPCRYPTO";
|
|
87
|
+
ArchiveDetail["Aes128"] = "AES_128";
|
|
88
|
+
ArchiveDetail["Aes192"] = "AES_192";
|
|
89
|
+
ArchiveDetail["Aes256"] = "AES_256";
|
|
90
|
+
ArchiveDetail["Decrypted"] = "DECRYPTED";
|
|
91
|
+
ArchiveDetail["EmptyPassword"] = "EMPTY_PASSWORD";
|
|
92
|
+
ArchiveDetail["PasswordRequired"] = "PASSWORD_REQUIRED";
|
|
93
|
+
ArchiveDetail["Unsupported"] = "UNSUPPORTED";
|
|
94
|
+
ArchiveDetail["Header"] = "HEADER";
|
|
95
|
+
// COMPRESSION
|
|
96
|
+
ArchiveDetail["UnsupportedMethod"] = "UNSUPPORTED_METHOD";
|
|
97
|
+
ArchiveDetail["UnsupportedFeature"] = "UNSUPPORTED_FEATURE";
|
|
98
|
+
ArchiveDetail["UnusualMethod"] = "UNUSUAL_METHOD";
|
|
99
|
+
ArchiveDetail["Unavailable"] = "UNAVAILABLE";
|
|
100
|
+
// ENTRY
|
|
101
|
+
ArchiveDetail["NoPlugin"] = "NO_PLUGIN";
|
|
102
|
+
ArchiveDetail["TypeMismatch"] = "TYPE_MISMATCH";
|
|
103
|
+
ArchiveDetail["PluginPassed"] = "PLUGIN_PASSED";
|
|
104
|
+
ArchiveDetail["PluginScrubbed"] = "PLUGIN_SCRUBBED";
|
|
105
|
+
ArchiveDetail["PluginFailed"] = "PLUGIN_FAILED";
|
|
106
|
+
ArchiveDetail["PluginRemoved"] = "PLUGIN_REMOVED";
|
|
107
|
+
// METADATA
|
|
108
|
+
ArchiveDetail["Comment"] = "COMMENT";
|
|
109
|
+
ArchiveDetail["Stripped"] = "STRIPPED";
|
|
110
|
+
// LIMIT
|
|
111
|
+
ArchiveDetail["FileSize"] = "FILE_SIZE";
|
|
112
|
+
ArchiveDetail["EntryCount"] = "ENTRY_COUNT";
|
|
113
|
+
ArchiveDetail["DecompressedSize"] = "DECOMPRESSED_SIZE";
|
|
114
|
+
ArchiveDetail["TotalDecompressedSize"] = "TOTAL_DECOMPRESSED_SIZE";
|
|
115
|
+
ArchiveDetail["Ratio"] = "RATIO";
|
|
116
|
+
ArchiveDetail["DictionarySize"] = "DICTIONARY_SIZE";
|
|
117
|
+
ArchiveDetail["NestingDepth"] = "NESTING_DEPTH";
|
|
118
|
+
ArchiveDetail["Time"] = "TIME";
|
|
119
|
+
// PROCESSING
|
|
120
|
+
ArchiveDetail["MemoryFallback"] = "MEMORY_FALLBACK";
|
|
121
|
+
ArchiveDetail["VerificationFailed"] = "VERIFICATION_FAILED";
|
|
122
|
+
ArchiveDetail["ContentRemoved"] = "CONTENT_REMOVED";
|
|
123
|
+
})(ArchiveDetail || (exports.ArchiveDetail = ArchiveDetail = {}));
|
|
124
|
+
var ArchiveRisk;
|
|
125
|
+
(function (ArchiveRisk) {
|
|
126
|
+
ArchiveRisk["None"] = "NONE";
|
|
127
|
+
ArchiveRisk["Low"] = "LOW";
|
|
128
|
+
ArchiveRisk["Medium"] = "MEDIUM";
|
|
129
|
+
ArchiveRisk["High"] = "HIGH";
|
|
130
|
+
ArchiveRisk["Critical"] = "CRITICAL";
|
|
131
|
+
/** The file could not be inspected: unparseable, an entry no password opens, or a limit stopped the run. */
|
|
132
|
+
ArchiveRisk["Unknown"] = "UNKNOWN";
|
|
133
|
+
})(ArchiveRisk || (exports.ArchiveRisk = ArchiveRisk = {}));
|
package/dist/unpack.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { EntryKind } from './model';
|
|
2
|
+
import type { ArchiveFormat, ArchiveLimits, ByteSource } from './types';
|
|
3
|
+
/** One entry of an archive, as `openArchive` lists it. */
|
|
4
|
+
export interface ArchiveReaderEntry {
|
|
5
|
+
/** Position in the archive's own order. */
|
|
6
|
+
index: number;
|
|
7
|
+
/**
|
|
8
|
+
* The name as the archive gives it, decoded, with no rule applied: it can climb out with "..", be absolute or repeat
|
|
9
|
+
* another entry's name. Folders end in "/". The one entry of a compressed file has the name the file stores, or "".
|
|
10
|
+
*/
|
|
11
|
+
name: string;
|
|
12
|
+
kind: EntryKind;
|
|
13
|
+
/** Decoded size the headers declare, or undefined for a compressed file, whose size is known only once read. */
|
|
14
|
+
size?: number;
|
|
15
|
+
/** Permission bits, when the format records them. */
|
|
16
|
+
mode?: number;
|
|
17
|
+
/** A link's target, when the entry is a link. */
|
|
18
|
+
linkTarget?: string;
|
|
19
|
+
/** True when the entry is encrypted. */
|
|
20
|
+
encrypted: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface OpenArchiveOptions {
|
|
23
|
+
/** Folder for temporary files, which an XZ entry of a ZIP archive needs. Defaults to `os.tmpdir()`. */
|
|
24
|
+
tempDir?: string;
|
|
25
|
+
/** Bytes an XZ entry of a ZIP archive holds in memory before a temporary file takes over. Default 8 MiB. */
|
|
26
|
+
memoryThreshold?: number;
|
|
27
|
+
/** One password or a list, tried in order on each encrypted entry, then the empty password. */
|
|
28
|
+
password?: string | string[];
|
|
29
|
+
/** The limits of `inspectArchive`, with its defaults: every read counts toward them, and the total is shared. */
|
|
30
|
+
limits?: ArchiveLimits;
|
|
31
|
+
}
|
|
32
|
+
/** An archive opened for reading. */
|
|
33
|
+
export interface ArchiveReader {
|
|
34
|
+
format: ArchiveFormat;
|
|
35
|
+
entries: ArchiveReaderEntry[];
|
|
36
|
+
/** The first entry with this exact name, or undefined. */
|
|
37
|
+
find(name: string): ArchiveReaderEntry | undefined;
|
|
38
|
+
/** An entry's content, decrypted and decoded, checked against its size and CRC as it goes. */
|
|
39
|
+
stream(entry: ArchiveReaderEntry | string): AsyncIterable<Uint8Array>;
|
|
40
|
+
/** An entry's whole content. */
|
|
41
|
+
bytes(entry: ArchiveReaderEntry | string): Promise<Buffer>;
|
|
42
|
+
/** An entry's whole content as text, UTF-8 unless `encoding` says otherwise. */
|
|
43
|
+
text(entry: ArchiveReaderEntry | string, encoding?: BufferEncoding): Promise<string>;
|
|
44
|
+
/** Closes the source. */
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Thrown when an archive cannot be read: not a format this package reads, a limit, a missing password, or data that
|
|
49
|
+
* does not decode. `category` and `detail` name the finding `inspectArchive` would report.
|
|
50
|
+
*/
|
|
51
|
+
export declare class ArchiveReadError extends Error {
|
|
52
|
+
readonly category: string;
|
|
53
|
+
readonly detail: string;
|
|
54
|
+
readonly data?: Record<string, string | number> | undefined;
|
|
55
|
+
constructor(message: string, category: string, detail: string, data?: Record<string, string | number> | undefined);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Opens an archive or a compressed file for reading. The format comes from the content, as for `inspectArchive`. The
|
|
59
|
+
* entries are listed at once; content is read only when asked for. The source is closed by `close()`, or at once when
|
|
60
|
+
* opening fails.
|
|
61
|
+
*/
|
|
62
|
+
export declare function openArchive(input: Uint8Array | ByteSource, options?: OpenArchiveOptions): Promise<ArchiveReader>;
|
|
63
|
+
/**
|
|
64
|
+
* Decodes a compressed file, such as gzip, bzip2, zstd, Brotli, xz or .lzma, to its content. An archive of several
|
|
65
|
+
* entries throws; `openArchive` reads those.
|
|
66
|
+
*/
|
|
67
|
+
export declare function decompress(input: Uint8Array | ByteSource, options?: OpenArchiveOptions): Promise<Buffer>;
|
package/dist/unpack.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ArchiveReadError = void 0;
|
|
4
|
+
exports.openArchive = openArchive;
|
|
5
|
+
exports.decompress = decompress;
|
|
6
|
+
// Reading archives and compressed files as a library: list the entries of any format this package reads and read
|
|
7
|
+
// their content, under the same limits the inspection uses. Nothing here applies the rules or writes files; the names
|
|
8
|
+
// are as the archive gives them.
|
|
9
|
+
const engine_1 = require("./engine");
|
|
10
|
+
const errors_1 = require("./errors");
|
|
11
|
+
const formats_1 = require("./formats");
|
|
12
|
+
const io_1 = require("./io");
|
|
13
|
+
/**
|
|
14
|
+
* Thrown when an archive cannot be read: not a format this package reads, a limit, a missing password, or data that
|
|
15
|
+
* does not decode. `category` and `detail` name the finding `inspectArchive` would report.
|
|
16
|
+
*/
|
|
17
|
+
class ArchiveReadError extends Error {
|
|
18
|
+
category;
|
|
19
|
+
detail;
|
|
20
|
+
data;
|
|
21
|
+
constructor(message, category, detail, data) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.category = category;
|
|
24
|
+
this.detail = detail;
|
|
25
|
+
this.data = data;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ArchiveReadError = ArchiveReadError;
|
|
29
|
+
/** The error a reader throws for what stopped it. Time and nesting limits and I/O errors pass as they are. */
|
|
30
|
+
function readError(e) {
|
|
31
|
+
if ((0, errors_1.stopsTree)(e) || e instanceof ArchiveReadError)
|
|
32
|
+
return e;
|
|
33
|
+
if (e instanceof errors_1.StopError)
|
|
34
|
+
return new ArchiveReadError(e.message, e.category, e.detail, e.data);
|
|
35
|
+
if (e instanceof errors_1.CheckError)
|
|
36
|
+
return new ArchiveReadError(e.message, 'CORRUPTED', 'CHECK_FAILED', { check: e.check });
|
|
37
|
+
if (e instanceof errors_1.DataError)
|
|
38
|
+
return new ArchiveReadError(e.message, 'CORRUPTED', e.truncated ? 'TRUNCATED' : 'DATA_ERROR');
|
|
39
|
+
if (e instanceof errors_1.UnsupportedError)
|
|
40
|
+
return new ArchiveReadError(e.message, 'COMPRESSION', e.method ? 'UNSUPPORTED_METHOD' : 'UNSUPPORTED_FEATURE');
|
|
41
|
+
return e;
|
|
42
|
+
}
|
|
43
|
+
const hasContent = (e) => e.kind === 'file' || e.kind === 'other';
|
|
44
|
+
/**
|
|
45
|
+
* Opens an archive or a compressed file for reading. The format comes from the content, as for `inspectArchive`. The
|
|
46
|
+
* entries are listed at once; content is read only when asked for. The source is closed by `close()`, or at once when
|
|
47
|
+
* opening fails.
|
|
48
|
+
*/
|
|
49
|
+
async function openArchive(input, options = {}) {
|
|
50
|
+
const source = input instanceof Uint8Array ? (0, io_1.bufferSource)(input) : input;
|
|
51
|
+
const limits = (0, engine_1.limitsOf)({ limits: options.limits });
|
|
52
|
+
const deadline = options.limits?.timeMs !== undefined ? Date.now() + options.limits.timeMs : undefined;
|
|
53
|
+
const passwords = (0, engine_1.passwordsOf)({ password: options.password });
|
|
54
|
+
const totals = { decoded: 0 };
|
|
55
|
+
const temp = new io_1.TempDir(options.tempDir);
|
|
56
|
+
let archive;
|
|
57
|
+
try {
|
|
58
|
+
const size = await source.size();
|
|
59
|
+
if (limits.fileSize !== undefined && size > limits.fileSize)
|
|
60
|
+
throw new ArchiveReadError('The file exceeds the file size limit', 'LIMIT', 'FILE_SIZE', { size });
|
|
61
|
+
archive = await (0, formats_1.openArchive)(source, limits, deadline, passwords, { dir: temp, threshold: options.memoryThreshold ?? 8 * 1024 * 1024 });
|
|
62
|
+
// ZIP and 7z keep a symbolic link's target as its content.
|
|
63
|
+
if (archive.linkTarget) {
|
|
64
|
+
for (const e of archive.entries) {
|
|
65
|
+
if (e.kind !== 'symlink' || e.broken || e.linkTarget !== undefined)
|
|
66
|
+
continue;
|
|
67
|
+
try {
|
|
68
|
+
e.linkTarget = await archive.linkTarget(e, (0, engine_1.makeMeter)(e, limits, deadline, totals));
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (!(err instanceof errors_1.DataError))
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
await source.close?.();
|
|
79
|
+
await temp.cleanup();
|
|
80
|
+
throw readError(e);
|
|
81
|
+
}
|
|
82
|
+
const opened = archive;
|
|
83
|
+
const listed = opened.entries.map(e => ({
|
|
84
|
+
index: e.index,
|
|
85
|
+
name: e.name,
|
|
86
|
+
kind: e.kind,
|
|
87
|
+
size: opened.single ? undefined : e.size,
|
|
88
|
+
mode: e.mode,
|
|
89
|
+
linkTarget: e.linkTarget,
|
|
90
|
+
encrypted: e.encryption !== undefined,
|
|
91
|
+
}));
|
|
92
|
+
const entryOf = (which) => {
|
|
93
|
+
const listedEntry = typeof which === 'string' ? listed.find(l => l.name === which) : which;
|
|
94
|
+
const e = listedEntry === undefined ? undefined : opened.entries[listedEntry.index];
|
|
95
|
+
if (!e)
|
|
96
|
+
throw new ArchiveReadError(`No entry ${JSON.stringify(typeof which === 'string' ? which : which.name)}`, 'ENTRY', 'NOT_FOUND');
|
|
97
|
+
if (!hasContent(e))
|
|
98
|
+
throw new ArchiveReadError(`Entry ${JSON.stringify(e.name)} is a ${e.kind}, which has no content`, 'ENTRY', 'NO_CONTENT');
|
|
99
|
+
return e;
|
|
100
|
+
};
|
|
101
|
+
/** The passwords that open an encrypted entry, in the order to try them; undefined alone for a plain one. */
|
|
102
|
+
async function* keys(e) {
|
|
103
|
+
if (!e.encryption) {
|
|
104
|
+
yield undefined;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (e.encryption.kind === 'unsupported')
|
|
108
|
+
throw new ArchiveReadError(`Entry ${JSON.stringify(e.name)} uses ${e.encryption.reason}`, 'ENCRYPTED', 'UNSUPPORTED');
|
|
109
|
+
let any = false;
|
|
110
|
+
for (const p of passwords) {
|
|
111
|
+
if (await opened.unlock(e, p)) {
|
|
112
|
+
any = true;
|
|
113
|
+
yield p;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!any)
|
|
117
|
+
throw new ArchiveReadError(`No password opens entry ${JSON.stringify(e.name)}`, 'ENCRYPTED', 'PASSWORD_REQUIRED');
|
|
118
|
+
}
|
|
119
|
+
async function* stream(which) {
|
|
120
|
+
const e = entryOf(which);
|
|
121
|
+
if (e.broken)
|
|
122
|
+
throw new ArchiveReadError(`Entry ${JSON.stringify(e.name)} cannot be read: ${e.broken}`, 'CORRUPTED', 'ENTRY_DATA');
|
|
123
|
+
try {
|
|
124
|
+
// A stream cannot take back what it gave, so it reads with the first password that opens the entry.
|
|
125
|
+
for await (const password of keys(e)) {
|
|
126
|
+
yield* opened.read(e, (0, engine_1.makeMeter)(e, limits, deadline, totals), password);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
throw readError(err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function bytes(which) {
|
|
135
|
+
const e = entryOf(which);
|
|
136
|
+
if (e.broken)
|
|
137
|
+
throw new ArchiveReadError(`Entry ${JSON.stringify(e.name)} cannot be read: ${e.broken}`, 'CORRUPTED', 'ENTRY_DATA');
|
|
138
|
+
let failure;
|
|
139
|
+
try {
|
|
140
|
+
// A ZipCrypto header can pass for a wrong password, so a password whose data fails gives way to the next.
|
|
141
|
+
for await (const password of keys(e)) {
|
|
142
|
+
const meter = (0, engine_1.makeMeter)(e, limits, deadline, totals);
|
|
143
|
+
const parts = [];
|
|
144
|
+
try {
|
|
145
|
+
for await (const c of opened.read(e, meter, password))
|
|
146
|
+
parts.push(c);
|
|
147
|
+
return Buffer.concat(parts);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
totals.decoded -= meter.total;
|
|
151
|
+
if (!(err instanceof errors_1.DataError) || e.encryption === undefined)
|
|
152
|
+
throw err;
|
|
153
|
+
failure = err;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
throw readError(err);
|
|
159
|
+
}
|
|
160
|
+
throw readError(failure);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
format: opened.format,
|
|
164
|
+
entries: listed,
|
|
165
|
+
find: name => listed.find(l => l.name === name),
|
|
166
|
+
stream,
|
|
167
|
+
bytes,
|
|
168
|
+
text: async (which, encoding = 'utf8') => (await bytes(which)).toString(encoding),
|
|
169
|
+
close: async () => {
|
|
170
|
+
try {
|
|
171
|
+
await source.close?.();
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
await temp.cleanup();
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Decodes a compressed file, such as gzip, bzip2, zstd, Brotli, xz or .lzma, to its content. An archive of several
|
|
181
|
+
* entries throws; `openArchive` reads those.
|
|
182
|
+
*/
|
|
183
|
+
async function decompress(input, options = {}) {
|
|
184
|
+
const reader = await openArchive(input, options);
|
|
185
|
+
try {
|
|
186
|
+
const formats = ['gzip', 'bzip2', 'zstd', 'brotli', 'xz', 'lzma'];
|
|
187
|
+
if (!formats.includes(reader.format))
|
|
188
|
+
throw new ArchiveReadError(`A ${reader.format} archive holds entries; openArchive reads them`, 'FORMAT', 'NOT_SINGLE');
|
|
189
|
+
return await reader.bytes(reader.entries[0]);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await reader.close();
|
|
193
|
+
}
|
|
194
|
+
}
|