1 | # node-stream-zip ![CI Checks](https://github.com/antelle/node-stream-zip/workflows/CI%20Checks/badge.svg)
|
2 |
|
3 | node.js library for reading and extraction of ZIP archives.
|
4 | Features:
|
5 |
|
6 | - it never loads entire archive into memory, everything is read by chunks
|
7 | - large archives support
|
8 | - all operations are non-blocking, no sync i/o
|
9 | - fast initialization
|
10 | - no dependencies, no binary addons
|
11 | - decompression with built-in zlib module
|
12 | - deflate, sfx, macosx/windows built-in archives
|
13 | - ZIP64 support
|
14 |
|
15 | ## Installation
|
16 |
|
17 | ```sh
|
18 | npm i node-stream-zip
|
19 | ```
|
20 |
|
21 | ## Usage
|
22 |
|
23 | There are two APIs provided:
|
24 | 1. [promise-based / async](#async-api)
|
25 | 2. [callbacks](#callback-api)
|
26 |
|
27 | It's recommended to use the new, promise API, however the legacy callback API
|
28 | may be more flexible for certain operations.
|
29 |
|
30 | ### Async API
|
31 |
|
32 | Open a zip file
|
33 | ```javascript
|
34 | const StreamZip = require('node-stream-zip');
|
35 | const zip = new StreamZip.async({ file: 'archive.zip' });
|
36 | ```
|
37 |
|
38 | Stream one entry to stdout
|
39 | ```javascript
|
40 | const stm = await zip.stream('path/inside/zip.txt');
|
41 | stm.pipe(process.stdout);
|
42 | stm.on('end', () => zip.close());
|
43 | ```
|
44 |
|
45 | Read a file as buffer
|
46 | ```javascript
|
47 | const data = await zip.entryData('path/inside/zip.txt');
|
48 | await zip.close();
|
49 | ```
|
50 |
|
51 | Extract one file to disk
|
52 | ```javascript
|
53 | await zip.extract('path/inside/zip.txt', './extracted.txt');
|
54 | await zip.close();
|
55 | ```
|
56 |
|
57 | List entries
|
58 | ```javascript
|
59 | const entriesCount = await zip.entriesCount;
|
60 | console.log(`Entries read: ${entriesCount}`);
|
61 |
|
62 | const entries = await zip.entries();
|
63 | for (const entry of Object.values(entries)) {
|
64 | const desc = entry.isDirectory ? 'directory' : `${entry.size} bytes`;
|
65 | console.log(`Entry ${entry.name}: ${desc}`);
|
66 | }
|
67 |
|
68 | // Do not forget to close the file once you're done
|
69 | await zip.close();
|
70 | ```
|
71 |
|
72 | Extract a folder from archive to disk
|
73 | ```javascript
|
74 | fs.mkdirSync('extracted');
|
75 | await zip.extract('path/inside/zip/', './extracted');
|
76 | await zip.close();
|
77 | ```
|
78 |
|
79 | Extract everything
|
80 | ```javascript
|
81 | fs.mkdirSync('extracted');
|
82 | const count = await zip.extract(null, './extracted');
|
83 | console.log(`Extracted ${count} entries`);
|
84 | await zip.close();
|
85 | ```
|
86 |
|
87 | When extracting a folder, you can listen to `extract` event
|
88 | ```javascript
|
89 | zip.on('extract', (entry, file) => {
|
90 | console.log(`Extracted ${entry.name} to ${file}`);
|
91 | });
|
92 | ```
|
93 |
|
94 | `entry` event is generated for every entry during loading
|
95 | ```javascript
|
96 | zip.on('entry', entry => {
|
97 | // you can already stream this entry,
|
98 | // without waiting until all entry descriptions are read (suitable for very large archives)
|
99 | console.log(`Read entry ${entry.name}`);
|
100 | });
|
101 | ```
|
102 |
|
103 | ### Callback API
|
104 |
|
105 | Open a zip file
|
106 | ```javascript
|
107 | const StreamZip = require('node-stream-zip');
|
108 | const zip = new StreamZip({ file: 'archive.zip' });
|
109 |
|
110 | // Handle errors
|
111 | zip.on('error', err => { /*...*/ });
|
112 | ```
|
113 |
|
114 | List entries
|
115 | ```javascript
|
116 | zip.on('ready', () => {
|
117 | console.log('Entries read: ' + zip.entriesCount);
|
118 | for (const entry of Object.values(zip.entries())) {
|
119 | const desc = entry.isDirectory ? 'directory' : `${entry.size} bytes`;
|
120 | console.log(`Entry ${entry.name}: ${desc}`);
|
121 | }
|
122 | // Do not forget to close the file once you're done
|
123 | zip.close();
|
124 | });
|
125 | ```
|
126 |
|
127 | Stream one entry to stdout
|
128 | ```javascript
|
129 | zip.on('ready', () => {
|
130 | zip.stream('path/inside/zip.txt', (err, stm) => {
|
131 | stm.pipe(process.stdout);
|
132 | stm.on('end', () => zip.close());
|
133 | });
|
134 | });
|
135 | ```
|
136 |
|
137 | Extract one file to disk
|
138 | ```javascript
|
139 | zip.on('ready', () => {
|
140 | zip.extract('path/inside/zip.txt', './extracted.txt', err => {
|
141 | console.log(err ? 'Extract error' : 'Extracted');
|
142 | zip.close();
|
143 | });
|
144 | });
|
145 | ```
|
146 |
|
147 | Extract a folder from archive to disk
|
148 | ```javascript
|
149 | zip.on('ready', () => {
|
150 | fs.mkdirSync('extracted');
|
151 | zip.extract('path/inside/zip/', './extracted', err => {
|
152 | console.log(err ? 'Extract error' : 'Extracted');
|
153 | zip.close();
|
154 | });
|
155 | });
|
156 | ```
|
157 |
|
158 | Extract everything
|
159 | ```javascript
|
160 | zip.on('ready', () => {
|
161 | fs.mkdirSync('extracted');
|
162 | zip.extract(null, './extracted', (err, count) => {
|
163 | console.log(err ? 'Extract error' : `Extracted ${count} entries`);
|
164 | zip.close();
|
165 | });
|
166 | });
|
167 | ```
|
168 |
|
169 | Read a file as buffer in sync way
|
170 | ```javascript
|
171 | zip.on('ready', () => {
|
172 | const data = zip.entryDataSync('path/inside/zip.txt');
|
173 | zip.close();
|
174 | });
|
175 | ```
|
176 |
|
177 | When extracting a folder, you can listen to `extract` event
|
178 | ```javascript
|
179 | zip.on('extract', (entry, file) => {
|
180 | console.log(`Extracted ${entry.name} to ${file}`);
|
181 | });
|
182 | ```
|
183 |
|
184 | `entry` event is generated for every entry during loading
|
185 | ```javascript
|
186 | zip.on('entry', entry => {
|
187 | // you can already stream this entry,
|
188 | // without waiting until all entry descriptions are read (suitable for very large archives)
|
189 | console.log(`Read entry ${entry.name}`);
|
190 | });
|
191 | ```
|
192 |
|
193 | ## Options
|
194 |
|
195 | You can pass these options to the constructor
|
196 | - `storeEntries: true` - you will be able to work with entries inside zip archive, otherwise the only way to access them is `entry` event
|
197 | - `skipEntryNameValidation: true` - by default, entry name is checked for malicious characters, like `../` or `c:\123`, pass this flag to disable validation errors
|
198 |
|
199 | ## Methods
|
200 |
|
201 | - `zip.entries()` - get all entries description
|
202 | - `zip.entry(name)` - get entry description by name
|
203 | - `zip.stream(entry, function(err, stm) { })` - get entry data reader stream
|
204 | - `zip.entryDataSync(entry)` - get entry data in sync way
|
205 | - `zip.close()` - cleanup after all entries have been read, streamed, extracted, and you don't need the archive
|
206 |
|
207 | ## Building
|
208 |
|
209 | The project doesn't require building. To run unit tests with [nodeunit](https://github.com/caolan/nodeunit):
|
210 | ```sh
|
211 | npm test
|
212 | ```
|
213 |
|
214 | ## Known issues
|
215 |
|
216 | - [utf8](https://github.com/rubyzip/rubyzip/wiki/Files-with-non-ascii-filenames) file names
|
217 | - AES encrypted files
|
218 |
|
219 | ## Contributors
|
220 |
|
221 | ZIP parsing code has been partially forked from [cthackers/adm-zip](https://github.com/cthackers/adm-zip) (MIT license).
|