w-zip 1.0.41 → 1.0.43
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 +33 -2
- package/dist/w-zip.umd.js +2 -2
- package/dist/w-zip.umd.js.map +1 -1
- package/docs/index.html +2 -2
- package/docs/m7z.html +2 -2
- package/docs/m7z.mjs.html +2 -2
- package/docs/mZip.html +541 -8
- package/docs/mZip.mjs.html +244 -4
- package/{g-7z.mjs → g_7z.mjs} +1 -1
- package/{g-zip.mjs → g_zip.mjs} +13 -1
- package/package.json +3 -3
- package/src/mZip.mjs +242 -2
- package/test/unit-zipRead.test.mjs +639 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import crypto from 'crypto'
|
|
4
|
+
import assert from 'assert'
|
|
5
|
+
import { configure, ZipWriter, ZipReader, Uint8ArrayReader, Uint8ArrayWriter, TextReader, TextWriter } from '@zip.js/zip.js'
|
|
6
|
+
import wz from '../src/WZip.mjs'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
//configure, 測試自行產製特殊壓縮檔時亦關閉web worker
|
|
10
|
+
configure({ useWebWorkers: false })
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
//makeZip, 以zip.js產製壓縮檔, items為[項目路徑, 字串或Uint8Array]陣列
|
|
14
|
+
async function makeZip(items, opt = {}) {
|
|
15
|
+
let zw = new ZipWriter(new Uint8ArrayWriter(), opt)
|
|
16
|
+
for (let [name, data] of items) {
|
|
17
|
+
let reader = typeof data === 'string' ? new TextReader(data) : new Uint8ArrayReader(data)
|
|
18
|
+
await zw.add(name, reader)
|
|
19
|
+
}
|
|
20
|
+
return zw.close()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
//replaceName, 同長度替換項目名稱, 須於本地標頭與中央目錄各命中1次, 用以造出zip.js寫入端不允許之名稱
|
|
25
|
+
function replaceName(u8, from, to) {
|
|
26
|
+
let buf = Buffer.from(u8)
|
|
27
|
+
let bFrom = Buffer.from(from)
|
|
28
|
+
let bTo = Buffer.from(to)
|
|
29
|
+
let n = 0
|
|
30
|
+
let i = buf.indexOf(bFrom)
|
|
31
|
+
while (i >= 0) {
|
|
32
|
+
bTo.copy(buf, i)
|
|
33
|
+
n++
|
|
34
|
+
i = buf.indexOf(bFrom, i + bTo.length)
|
|
35
|
+
}
|
|
36
|
+
assert.strict.equal(n, 2, `replaceName ${from} -> ${to} hit ${n}`)
|
|
37
|
+
return buf
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
//walkSrc, 由來源資料夾列出zipFolder應產生之項目(以來源資料夾名稱為根, 子資料夾為目錄項)
|
|
42
|
+
function walkSrc(fdSrc) {
|
|
43
|
+
let bn = path.basename(fdSrc)
|
|
44
|
+
let r = []
|
|
45
|
+
let walk = (fd, rel) => {
|
|
46
|
+
for (let d of fs.readdirSync(fd, { withFileTypes: true })) {
|
|
47
|
+
let p = path.join(fd, d.name)
|
|
48
|
+
let name = `${bn}/${rel}${d.name}`
|
|
49
|
+
if (d.isDirectory()) {
|
|
50
|
+
r.push({ filename: name + '/', directory: true })
|
|
51
|
+
walk(p, `${rel}${d.name}/`)
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
r.push({ filename: name, directory: false, uncompressedSize: fs.statSync(p).size })
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
walk(fdSrc, '')
|
|
59
|
+
return r
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
//listFiles, 遞迴列出資料夾內所有檔案相對路徑, 用以確認讀取不寫入磁碟
|
|
64
|
+
function listFiles(fd) {
|
|
65
|
+
return fs.readdirSync(fd, { recursive: true }).map((v) => String(v)).sort()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
//spyFs, 攔截fs.promises.open以統計開啟/關閉之檔案代號數、實際讀取位元組數與單次讀取緩衝區最大長度, 結束後還原
|
|
70
|
+
//opt.maxChunk: 限制每次read最多回傳之位元組數, 用以模擬短讀
|
|
71
|
+
async function spyFs(fun, opt = {}) {
|
|
72
|
+
let open0 = fs.promises.open
|
|
73
|
+
let st = { opened: 0, closed: 0, bytesRead: 0, maxBuffer: 0, result: undefined, error: undefined }
|
|
74
|
+
fs.promises.open = async (...args) => {
|
|
75
|
+
let fh = await open0.apply(fs.promises, args)
|
|
76
|
+
st.opened++
|
|
77
|
+
let read0 = fh.read.bind(fh)
|
|
78
|
+
let close0 = fh.close.bind(fh)
|
|
79
|
+
fh.read = async (buffer, offset, length, position) => {
|
|
80
|
+
st.maxBuffer = Math.max(st.maxBuffer, buffer.length)
|
|
81
|
+
if (opt.maxChunk) {
|
|
82
|
+
length = Math.min(length, opt.maxChunk)
|
|
83
|
+
}
|
|
84
|
+
let r = await read0(buffer, offset, length, position)
|
|
85
|
+
st.bytesRead += r.bytesRead
|
|
86
|
+
return r
|
|
87
|
+
}
|
|
88
|
+
fh.close = async () => {
|
|
89
|
+
st.closed++
|
|
90
|
+
return close0()
|
|
91
|
+
}
|
|
92
|
+
return fh
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
st.result = await fun()
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
st.error = err
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
fs.promises.open = open0
|
|
102
|
+
}
|
|
103
|
+
return st
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
//setMethod, 改寫唯一項目之壓縮方法欄位(本地標頭offset 8、中央目錄offset 10), 用以造出不支援之壓縮方法
|
|
108
|
+
function setMethod(u8, method) {
|
|
109
|
+
let buf = Buffer.from(u8)
|
|
110
|
+
let iL = buf.indexOf(Buffer.from([0x50, 0x4b, 0x03, 0x04]))
|
|
111
|
+
let iC = buf.indexOf(Buffer.from([0x50, 0x4b, 0x01, 0x02]))
|
|
112
|
+
buf.writeUInt16LE(method, iL + 8)
|
|
113
|
+
buf.writeUInt16LE(method, iC + 10)
|
|
114
|
+
return buf
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
//setCdLength, 改寫EOCD之中央目錄長度欄位, 用以造出欄位異常之壓縮檔
|
|
119
|
+
function setCdLength(u8, len) {
|
|
120
|
+
let buf = Buffer.from(u8)
|
|
121
|
+
let i = buf.lastIndexOf(Buffer.from([0x50, 0x4b, 0x05, 0x06]))
|
|
122
|
+
buf.writeUInt32LE(len, i + 12)
|
|
123
|
+
return buf
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
//findFalseAcceptPassword, 找出可通過ZipCrypto單一位元組密碼驗證之錯誤密碼(約1/256), 用以驗證錯誤密碼不會讀出亂碼
|
|
128
|
+
async function findFalseAcceptPassword(u8, name) {
|
|
129
|
+
let zr = new ZipReader(new Uint8ArrayReader(u8))
|
|
130
|
+
let entry = (await zr.getEntries()).find((v) => v.filename === name)
|
|
131
|
+
let r = null
|
|
132
|
+
for (let i = 0; i < 20000 && r === null; i++) {
|
|
133
|
+
let p = `wrong${i}`
|
|
134
|
+
try {
|
|
135
|
+
await entry.getData(new Uint8ArrayWriter(), { password: p, checkPasswordOnly: true })
|
|
136
|
+
r = p
|
|
137
|
+
}
|
|
138
|
+
catch (err) {}
|
|
139
|
+
}
|
|
140
|
+
await zr.close()
|
|
141
|
+
assert.ok(r !== null, 'no false-accept password found')
|
|
142
|
+
return r
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
//getRejection, 取得reject值, 若resolve則測試失敗
|
|
147
|
+
async function getRejection(p) {
|
|
148
|
+
try {
|
|
149
|
+
await p
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
return err
|
|
153
|
+
}
|
|
154
|
+
assert.fail('expected reject but resolved')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
describe('zipRead', function() {
|
|
159
|
+
|
|
160
|
+
let fdTmp = './test/_tmp/zipRead'
|
|
161
|
+
let fpSrcTxt = './test/input/file1(中文).txt'
|
|
162
|
+
let fdSrc = './test/input/folder1'
|
|
163
|
+
let pw = 'abc'
|
|
164
|
+
|
|
165
|
+
let fpZipTxt = `${fdTmp}/txt.zip`
|
|
166
|
+
let fpZipFolder = `${fdTmp}/folder1.zip`
|
|
167
|
+
let fpZipPW = `${fdTmp}/folder1PW.zip`
|
|
168
|
+
let fpZipAES = `${fdTmp}/aes.zip`
|
|
169
|
+
let fpZipBOM = `${fdTmp}/bom.zip`
|
|
170
|
+
let fpZipDup = `${fdTmp}/dup.zip`
|
|
171
|
+
let fpZipSlip = `${fdTmp}/slip.zip`
|
|
172
|
+
let fpZipEmpty = `${fdTmp}/empty.zip`
|
|
173
|
+
let fpZipDocx = `${fdTmp}/sample.docx`
|
|
174
|
+
let fpZipBig = `${fdTmp}/big.zip`
|
|
175
|
+
let fpZipStorePW = `${fdTmp}/storePW.zip`
|
|
176
|
+
let fpZipCorrupt = `${fdTmp}/corrupt.zip`
|
|
177
|
+
let fpZipCorruptNoDD = `${fdTmp}/corruptNoDD.zip`
|
|
178
|
+
let fpZipNoDD = `${fdTmp}/noDD.zip`
|
|
179
|
+
let fpZipCdLength = `${fdTmp}/cdLength.zip`
|
|
180
|
+
let fpZipBackslash = `${fdTmp}/backslash.zip`
|
|
181
|
+
let fpZipMethod = `${fdTmp}/method12.zip`
|
|
182
|
+
let fpZipAbs = `${fdTmp}/abs.zip`
|
|
183
|
+
let fpZipDrive = `${fdTmp}/drive.zip`
|
|
184
|
+
let fpZipAppendSmall = `${fdTmp}/appendSmall.zip`
|
|
185
|
+
let fpZipAppendLarge = `${fdTmp}/appendLarge.zip`
|
|
186
|
+
let fpZipPrepend = `${fdTmp}/prepend.zip`
|
|
187
|
+
let fpZip64 = `${fdTmp}/zip64.zip`
|
|
188
|
+
let fpZipZeroByte = `${fdTmp}/zeroByte.zip`
|
|
189
|
+
let txtFox = 'The quick brown fox jumps over the lazy dog'
|
|
190
|
+
|
|
191
|
+
let xmlDocx = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document><w:body><w:tbl><w:tr><w:tc><w:p><w:r><w:t>表格內容 cell</w:t></w:r></w:p></w:tc></w:tr></w:tbl></w:body></w:document>'
|
|
192
|
+
|
|
193
|
+
before(async function() {
|
|
194
|
+
fs.rmSync(fdTmp, { recursive: true, force: true })
|
|
195
|
+
fs.mkdirSync(fdTmp, { recursive: true })
|
|
196
|
+
|
|
197
|
+
//w-zip產製: 單檔, 資料夾(含目錄項), 資料夾加密(ZipCrypto)
|
|
198
|
+
await wz.mZip.zipFile(fpSrcTxt, fpZipTxt)
|
|
199
|
+
await wz.mZip.zipFolder(fdSrc, fpZipFolder)
|
|
200
|
+
await wz.mZip.zipFolder(fdSrc, fpZipPW, { pw })
|
|
201
|
+
|
|
202
|
+
//zip.js產製: AES加密, 含BOM, 空壓縮檔, docx結構
|
|
203
|
+
fs.writeFileSync(fpZipAES, await makeZip([['a.txt', 'aes content']], { password: pw, encryptionStrength: 3 }))
|
|
204
|
+
let u8BOM = new Uint8Array([0xEF, 0xBB, 0xBF, ...new TextEncoder().encode('<a>中文</a>')])
|
|
205
|
+
fs.writeFileSync(fpZipBOM, await makeZip([['bom.xml', u8BOM]]))
|
|
206
|
+
fs.writeFileSync(fpZipEmpty, await makeZip([]))
|
|
207
|
+
fs.writeFileSync(fpZipDocx, await makeZip([
|
|
208
|
+
['[Content_Types].xml', '<?xml version="1.0" encoding="UTF-8"?><Types/>'],
|
|
209
|
+
['word/document.xml', xmlDocx],
|
|
210
|
+
['word/_rels/document.xml.rels', '<?xml version="1.0" encoding="UTF-8"?><Relationships/>'],
|
|
211
|
+
['word/media/image1.png', crypto.randomBytes(2048)],
|
|
212
|
+
['word/media/image2.jpeg', crypto.randomBytes(4096)],
|
|
213
|
+
]))
|
|
214
|
+
|
|
215
|
+
//同名項目: 以dup2.txt寫入後改名為dup1.txt, 中央目錄依序為first、second
|
|
216
|
+
fs.writeFileSync(fpZipDup, replaceName(await makeZip([['dup1.txt', 'first'], ['dup2.txt', 'second']], { level: 0 }), 'dup2.txt', 'dup1.txt'))
|
|
217
|
+
|
|
218
|
+
//不安全路徑: 以aa/evil.txt寫入後改名為../evil.txt
|
|
219
|
+
fs.writeFileSync(fpZipSlip, replaceName(await makeZip([['ok.txt', 'ok'], ['aa/evil.txt', 'evil']], { level: 0 }), 'aa/evil.txt', '../evil.txt'))
|
|
220
|
+
|
|
221
|
+
//大檔: 20MB不可壓縮內容加一個小項目
|
|
222
|
+
fs.writeFileSync(fpZipBig, await makeZip([['big.bin', crypto.randomBytes(20 * 1024 * 1024)], ['small.txt', 'small']], { level: 0 }))
|
|
223
|
+
|
|
224
|
+
//ZipCrypto未壓縮項目: 錯誤密碼約1/256可通過單一位元組驗證, 須靠CRC擋下
|
|
225
|
+
fs.writeFileSync(fpZipStorePW, await makeZip([['s.xlsx', fs.readFileSync(`${fdSrc}/f1-1.xlsx`)]], { level: 0, password: pw, zipCrypto: true }))
|
|
226
|
+
|
|
227
|
+
//資料區損毀: 未壓縮項目翻轉1位元組, 有無資料描述子各一
|
|
228
|
+
for (let [fp, opt] of [[fpZipCorrupt, { level: 0 }], [fpZipCorruptNoDD, { level: 0, dataDescriptor: false }]]) {
|
|
229
|
+
let buf = Buffer.from(await makeZip([['t.txt', txtFox]], opt))
|
|
230
|
+
let i = buf.indexOf(Buffer.from('quick'))
|
|
231
|
+
buf[i] = buf[i] ^ 0x20
|
|
232
|
+
fs.writeFileSync(fp, buf)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
//無資料描述子: 同Word產製之docx
|
|
236
|
+
fs.writeFileSync(fpZipNoDD, await makeZip([['n.txt', 'no data descriptor'], ['n.xlsx', fs.readFileSync(`${fdSrc}/f1-1.xlsx`)]], { dataDescriptor: false }))
|
|
237
|
+
|
|
238
|
+
//中央目錄長度欄位異常: 宣告為1GiB
|
|
239
|
+
fs.writeFileSync(fpZipCdLength, setCdLength(await makeZip([['a.txt', 'aaa'], ['b.txt', 'bbb']]), 0x40000000))
|
|
240
|
+
|
|
241
|
+
//反斜線分隔之項目名: 以'/'寫入後改為'\'
|
|
242
|
+
let zwBs = new ZipWriter(new Uint8ArrayWriter(), { level: 0 })
|
|
243
|
+
await zwBs.add('a/b.txt', new TextReader('bbb'))
|
|
244
|
+
await zwBs.add('dir/', undefined, { directory: true })
|
|
245
|
+
fs.writeFileSync(fpZipBackslash, replaceName(replaceName(await zwBs.close(), 'a/b.txt', 'a\\b.txt'), 'dir/', 'dir\\'))
|
|
246
|
+
|
|
247
|
+
//不支援之壓縮方法(12為BZip2), 不安全路徑之其他形態(絕對路徑, 磁碟代號)
|
|
248
|
+
fs.writeFileSync(fpZipMethod, setMethod(await makeZip([['m.txt', 'method']], { level: 0 }), 12))
|
|
249
|
+
fs.writeFileSync(fpZipAbs, replaceName(await makeZip([['xabs.txt', 'x']], { level: 0 }), 'xabs.txt', '/abs.txt'))
|
|
250
|
+
fs.writeFileSync(fpZipDrive, replaceName(await makeZip([['Cxx.txt', 'x']], { level: 0 }), 'Cxx.txt', 'C:x.txt'))
|
|
251
|
+
|
|
252
|
+
//附加資料(EOCD之後)少量與超量, 前置資料(類自解壓檔), 強制zip64, 0位元組項目
|
|
253
|
+
let bTxt = fs.readFileSync(fpZipTxt)
|
|
254
|
+
fs.writeFileSync(fpZipAppendSmall, Buffer.concat([bTxt, Buffer.alloc(1000)]))
|
|
255
|
+
fs.writeFileSync(fpZipAppendLarge, Buffer.concat([bTxt, Buffer.alloc(70000)]))
|
|
256
|
+
fs.writeFileSync(fpZipPrepend, Buffer.concat([Buffer.alloc(5000, 0x4d), bTxt]))
|
|
257
|
+
fs.writeFileSync(fpZip64, await makeZip([['z.txt', 'zip64']], { zip64: true }))
|
|
258
|
+
fs.writeFileSync(fpZipZeroByte, await makeZip([['empty.txt', '']]))
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
after(function() {
|
|
262
|
+
fs.rmSync(fdTmp, { recursive: true, force: true })
|
|
263
|
+
try {
|
|
264
|
+
fs.rmdirSync('./test/_tmp') //僅於已無其他測試暫存時移除
|
|
265
|
+
}
|
|
266
|
+
catch (err) {}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
describe('listEntries', function() {
|
|
270
|
+
|
|
271
|
+
it('P01a 列出zipFolder之全部項目, 含目錄項與正確大小', async function() {
|
|
272
|
+
let r = await wz.mZip.listEntries(fpZipFolder)
|
|
273
|
+
let exp = walkSrc(fdSrc).sort((a, b) => a.filename.localeCompare(b.filename))
|
|
274
|
+
let act = r.map((v) => {
|
|
275
|
+
return v.directory ? { filename: v.filename, directory: true } : { filename: v.filename, directory: false, uncompressedSize: v.uncompressedSize }
|
|
276
|
+
}).sort((a, b) => a.filename.localeCompare(b.filename))
|
|
277
|
+
assert.strict.deepEqual(act, exp)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('P01b 每項僅含6個欄位且型別正確, 不含size與getData', async function() {
|
|
281
|
+
let r = await wz.mZip.listEntries(fpZipFolder)
|
|
282
|
+
assert.ok(r.length > 0)
|
|
283
|
+
for (let v of r) {
|
|
284
|
+
assert.strict.deepEqual(Object.keys(v).sort(), ['compressedSize', 'directory', 'encrypted', 'filename', 'lastModDate', 'uncompressedSize'])
|
|
285
|
+
assert.strict.equal(typeof v.filename, 'string')
|
|
286
|
+
assert.strict.equal(typeof v.directory, 'boolean')
|
|
287
|
+
assert.strict.equal(typeof v.uncompressedSize, 'number')
|
|
288
|
+
assert.strict.equal(typeof v.compressedSize, 'number')
|
|
289
|
+
assert.strict.equal(v.encrypted, false)
|
|
290
|
+
assert.ok(v.lastModDate instanceof Date)
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('P01d 空壓縮檔回傳空陣列', async function() {
|
|
295
|
+
assert.strict.deepEqual(await wz.mZip.listEntries(fpZipEmpty), [])
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('P03a Buffer與非Buffer之Uint8Array結果同檔案路徑', async function() {
|
|
299
|
+
let rPath = await wz.mZip.listEntries(fpZipFolder)
|
|
300
|
+
let b = fs.readFileSync(fpZipFolder)
|
|
301
|
+
assert.strict.deepEqual(await wz.mZip.listEntries(b), rPath)
|
|
302
|
+
assert.strict.deepEqual(await wz.mZip.listEntries(new Uint8Array(b)), rPath)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('P05c 加密壓縮檔(ZipCrypto與AES)不需密碼即可列出, 檔案項encrypted為true', async function() {
|
|
306
|
+
for (let fp of [fpZipPW, fpZipAES]) {
|
|
307
|
+
let r = await wz.mZip.listEntries(fp)
|
|
308
|
+
let files = r.filter((v) => !v.directory)
|
|
309
|
+
assert.ok(files.length > 0)
|
|
310
|
+
assert.ok(files.every((v) => v.encrypted === true), fp)
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('P06b 同名項目兩筆皆列出', async function() {
|
|
315
|
+
let r = await wz.mZip.listEntries(fpZipDup)
|
|
316
|
+
assert.strict.deepEqual(r.map((v) => v.filename), ['dup1.txt', 'dup1.txt'])
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('P03b 無效src型別reject為invalid src', async function() {
|
|
320
|
+
for (let src of [123, null, undefined, {}, new ArrayBuffer(8)]) {
|
|
321
|
+
assert.strict.equal(await getRejection(wz.mZip.listEntries(src)), 'invalid src')
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('P03c 路徑不存在或為資料夾時reject', async function() {
|
|
326
|
+
assert.strict.equal(await getRejection(wz.mZip.listEntries(`${fdTmp}/nope.zip`)), 'invalid path of source file')
|
|
327
|
+
assert.strict.equal(await getRejection(wz.mZip.listEntries('')), 'invalid path of source file')
|
|
328
|
+
assert.strict.equal(await getRejection(wz.mZip.listEntries(fdTmp)), 'path of source is not file')
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('P04c 非zip、截斷zip、空Buffer時reject', async function() {
|
|
332
|
+
let b = fs.readFileSync(fpZipFolder)
|
|
333
|
+
for (let src of [fpSrcTxt, b.subarray(0, b.length - 30), Buffer.alloc(0)]) {
|
|
334
|
+
assert.ok(await getRejection(wz.mZip.listEntries(src)) instanceof Error)
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('P04f 含不安全路徑之壓縮檔整個reject', async function() {
|
|
339
|
+
let err = await getRejection(wz.mZip.listEntries(fpZipSlip))
|
|
340
|
+
assert.match(err.message, /Unsafe filename/)
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it('P04f 其他不安全路徑(絕對路徑、磁碟代號)亦整個reject', async function() {
|
|
344
|
+
for (let fp of [fpZipAbs, fpZipDrive]) {
|
|
345
|
+
assert.match((await getRejection(wz.mZip.listEntries(fp))).message, /Unsafe filename/, fp)
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('P11a 以反斜線分隔之項目名正規化為/', async function() {
|
|
350
|
+
let r = await wz.mZip.listEntries(fpZipBackslash)
|
|
351
|
+
assert.strict.deepEqual(r.map((v) => [v.filename, v.directory]), [['a/b.txt', false], ['dir/', true]])
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('P11b 中央目錄長度欄位異常時路徑與Buffer結果一致, 且不依宣告長度配置緩衝區', async function() {
|
|
355
|
+
let st = await spyFs(() => wz.mZip.listEntries(fpZipCdLength))
|
|
356
|
+
assert.strict.equal(st.error, undefined)
|
|
357
|
+
assert.strict.deepEqual(st.result.map((v) => v.filename), ['a.txt', 'b.txt'])
|
|
358
|
+
assert.strict.deepEqual(await wz.mZip.listEntries(fs.readFileSync(fpZipCdLength)), st.result)
|
|
359
|
+
assert.ok(st.maxBuffer <= fs.statSync(fpZipCdLength).size, `maxBuffer ${st.maxBuffer}`)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it('P11c 不支援之壓縮方法仍可列出', async function() {
|
|
363
|
+
assert.strict.deepEqual((await wz.mZip.listEntries(fpZipMethod)).map((v) => v.filename), ['m.txt'])
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it('P11e EOCD後附加資料: 1000位元組可列出, 70000位元組reject', async function() {
|
|
367
|
+
assert.strict.deepEqual((await wz.mZip.listEntries(fpZipAppendSmall)).map((v) => v.filename), ['file1(中文).txt'])
|
|
368
|
+
for (let src of [fpZipAppendLarge, fs.readFileSync(fpZipAppendLarge)]) {
|
|
369
|
+
assert.match((await getRejection(wz.mZip.listEntries(src))).message, /Ambiguous archive/)
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('P11f 前置資料(類自解壓檔)與zip64可列出, 路徑與Buffer一致', async function() {
|
|
374
|
+
for (let [fp, names] of [[fpZipPrepend, ['file1(中文).txt']], [fpZip64, ['z.txt']]]) {
|
|
375
|
+
let r = await wz.mZip.listEntries(fp)
|
|
376
|
+
assert.strict.deepEqual(r.map((v) => v.filename), names)
|
|
377
|
+
assert.strict.deepEqual(await wz.mZip.listEntries(fs.readFileSync(fp)), r)
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
describe('readEntry', function() {
|
|
384
|
+
|
|
385
|
+
it('P02a 預設與text回傳UTF-8字串且等於來源', async function() {
|
|
386
|
+
let exp = fs.readFileSync(fpSrcTxt, 'utf8')
|
|
387
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipTxt, 'file1(中文).txt'), exp)
|
|
388
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipTxt, 'file1(中文).txt', { type: 'text' }), exp)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
it('P02b P02d u8回傳Uint8Array且位元組等於來源(含中文檔名)', async function() {
|
|
392
|
+
for (let rel of ['f1-1.xlsx', 'folder2/f2-3(中文).xlsx']) {
|
|
393
|
+
let r = await wz.mZip.readEntry(fpZipFolder, `folder1/${rel}`, { type: 'u8' })
|
|
394
|
+
assert.ok(r instanceof Uint8Array)
|
|
395
|
+
assert.ok(Buffer.from(r).equals(fs.readFileSync(`${fdSrc}/${rel}`)), rel)
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('P02c 含BOM之項目text去除BOM, u8保留原始位元組', async function() {
|
|
400
|
+
let t = await wz.mZip.readEntry(fpZipBOM, 'bom.xml')
|
|
401
|
+
assert.strict.equal(t, '<a>中文</a>')
|
|
402
|
+
let u = await wz.mZip.readEntry(fpZipBOM, 'bom.xml', { type: 'u8' })
|
|
403
|
+
assert.strict.deepEqual([...u.subarray(0, 3)], [0xEF, 0xBB, 0xBF])
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it('P03a Buffer與非Buffer之Uint8Array結果同檔案路徑', async function() {
|
|
407
|
+
let b = fs.readFileSync(fpZipTxt)
|
|
408
|
+
let exp = await wz.mZip.readEntry(fpZipTxt, 'file1(中文).txt')
|
|
409
|
+
assert.strict.equal(await wz.mZip.readEntry(b, 'file1(中文).txt'), exp)
|
|
410
|
+
assert.strict.equal(await wz.mZip.readEntry(new Uint8Array(b), 'file1(中文).txt'), exp)
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('P04a 找不到項目時reject並含項目名稱', async function() {
|
|
414
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fpZipFolder, 'nope.txt')), 'entry not found: nope.txt')
|
|
415
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fpZipEmpty, 'nope.txt')), 'entry not found: nope.txt')
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
it('P04b 項目為資料夾時reject', async function() {
|
|
419
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fpZipFolder, 'folder1/folder2/')), 'entry is directory: folder1/folder2/')
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
it('P06a 以/路徑精確比對, 反斜線、前導/、大小寫不同皆視為找不到', async function() {
|
|
423
|
+
for (let name of ['folder1\\folder2\\f2-1.xlsx', '/folder1/f1-1.xlsx', 'FOLDER1/f1-1.xlsx']) {
|
|
424
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fpZipFolder, name)), `entry not found: ${name}`)
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('P11a 以反斜線分隔之項目以/路徑讀取', async function() {
|
|
429
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipBackslash, 'a/b.txt'), 'bbb')
|
|
430
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fpZipBackslash, 'dir/')), 'entry is directory: dir/')
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it('P05e ZipCrypto未壓縮項目: 可通過驗證位元組之錯誤密碼仍reject, 正確密碼可讀', async function() {
|
|
434
|
+
let pwFalse = await findFalseAcceptPassword(fs.readFileSync(fpZipStorePW), 's.xlsx')
|
|
435
|
+
assert.match((await getRejection(wz.mZip.readEntry(fpZipStorePW, 's.xlsx', { type: 'u8', pw: pwFalse }))).message, /Invalid CRC32/)
|
|
436
|
+
let r = await wz.mZip.readEntry(fpZipStorePW, 's.xlsx', { type: 'u8', pw })
|
|
437
|
+
assert.ok(Buffer.from(r).equals(fs.readFileSync(`${fdSrc}/f1-1.xlsx`)))
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('P05f 資料區損毀時reject, 有無資料描述子皆同, 列出不受影響', async function() {
|
|
441
|
+
for (let fp of [fpZipCorrupt, fpZipCorruptNoDD]) {
|
|
442
|
+
assert.match((await getRejection(wz.mZip.readEntry(fp, 't.txt'))).message, /Invalid CRC32/, fp)
|
|
443
|
+
assert.strict.deepEqual((await wz.mZip.listEntries(fp)).map((v) => v.filename), ['t.txt'])
|
|
444
|
+
}
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it('P11g 無資料描述子之項目可讀(同Word產製之docx)', async function() {
|
|
448
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipNoDD, 'n.txt'), 'no data descriptor')
|
|
449
|
+
let r = await wz.mZip.readEntry(fpZipNoDD, 'n.xlsx', { type: 'u8' })
|
|
450
|
+
assert.ok(Buffer.from(r).equals(fs.readFileSync(`${fdSrc}/f1-1.xlsx`)))
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('P11c 不支援之壓縮方法reject', async function() {
|
|
454
|
+
assert.match((await getRejection(wz.mZip.readEntry(fpZipMethod, 'm.txt'))).message, /Compression method not supported/)
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('P11e P11f 附加少量資料、前置資料、zip64之項目可讀', async function() {
|
|
458
|
+
let exp = fs.readFileSync(fpSrcTxt, 'utf8')
|
|
459
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipAppendSmall, 'file1(中文).txt'), exp)
|
|
460
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipPrepend, 'file1(中文).txt'), exp)
|
|
461
|
+
assert.strict.equal(await wz.mZip.readEntry(fs.readFileSync(fpZipPrepend), 'file1(中文).txt'), exp)
|
|
462
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZip64, 'z.txt'), 'zip64')
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
it('P11h 0位元組項目: text為空字串, u8長度為0', async function() {
|
|
466
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipZeroByte, 'empty.txt'), '')
|
|
467
|
+
let u = await wz.mZip.readEntry(fpZipZeroByte, 'empty.txt', { type: 'u8' })
|
|
468
|
+
assert.ok(u instanceof Uint8Array)
|
|
469
|
+
assert.strict.equal(u.length, 0)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it('P11i 具byteOffset之Buffer視圖可讀', async function() {
|
|
473
|
+
let b = fs.readFileSync(fpZipTxt)
|
|
474
|
+
let big = Buffer.alloc(b.length + 100)
|
|
475
|
+
b.copy(big, 37)
|
|
476
|
+
let view = big.subarray(37, 37 + b.length)
|
|
477
|
+
assert.strict.equal(view.byteOffset, 37)
|
|
478
|
+
assert.strict.equal(await wz.mZip.readEntry(view, 'file1(中文).txt'), fs.readFileSync(fpSrcTxt, 'utf8'))
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
it('P04g opt.pw非字串時reject', async function() {
|
|
482
|
+
for (let p of [123, true, null, {}]) {
|
|
483
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fpZipTxt, 'file1(中文).txt', { pw: p })), 'invalid opt.pw')
|
|
484
|
+
}
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it('P06b 同名項目取中央目錄最後一筆', async function() {
|
|
488
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipDup, 'dup1.txt'), 'second')
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
it('P05a ZipCrypto加密: 正確密碼可讀, 無密碼或空字串密碼或錯誤密碼reject', async function() {
|
|
492
|
+
let name = 'folder1/f1-1.xlsx'
|
|
493
|
+
let r = await wz.mZip.readEntry(fpZipPW, name, { type: 'u8', pw })
|
|
494
|
+
assert.ok(Buffer.from(r).equals(fs.readFileSync(`${fdSrc}/f1-1.xlsx`)))
|
|
495
|
+
assert.match((await getRejection(wz.mZip.readEntry(fpZipPW, name, { type: 'u8' }))).message, /encrypted entry/)
|
|
496
|
+
assert.match((await getRejection(wz.mZip.readEntry(fpZipPW, name, { type: 'u8', pw: '' }))).message, /encrypted entry/)
|
|
497
|
+
assert.ok(await getRejection(wz.mZip.readEntry(fpZipPW, name, { type: 'u8', pw: 'abd' })) instanceof Error)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
it('P05b AES加密: 正確密碼可讀, 無密碼或錯誤密碼reject', async function() {
|
|
501
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipAES, 'a.txt', { pw }), 'aes content')
|
|
502
|
+
assert.match((await getRejection(wz.mZip.readEntry(fpZipAES, 'a.txt'))).message, /encrypted entry/)
|
|
503
|
+
assert.ok(await getRejection(wz.mZip.readEntry(fpZipAES, 'a.txt', { pw: 'abd' })) instanceof Error)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
it('P05d 未加密項目給予密碼仍可讀', async function() {
|
|
507
|
+
assert.strict.equal(await wz.mZip.readEntry(fpZipTxt, 'file1(中文).txt', { pw }), fs.readFileSync(fpSrcTxt, 'utf8'))
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
it('P03b 無效src型別reject為invalid src', async function() {
|
|
511
|
+
for (let src of [123, null, undefined, {}, new ArrayBuffer(8)]) {
|
|
512
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(src, 'a.txt')), 'invalid src')
|
|
513
|
+
}
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
it('P03c 路徑不存在或為資料夾時reject', async function() {
|
|
517
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(`${fdTmp}/nope.zip`, 'a.txt')), 'invalid path of source file')
|
|
518
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry('', 'a.txt')), 'invalid path of source file')
|
|
519
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fdTmp, 'a.txt')), 'path of source is not file')
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
it('P04c 非zip、截斷zip、空Buffer時reject', async function() {
|
|
523
|
+
let b = fs.readFileSync(fpZipFolder)
|
|
524
|
+
for (let src of [fpSrcTxt, b.subarray(0, b.length - 30), Buffer.alloc(0)]) {
|
|
525
|
+
assert.ok(await getRejection(wz.mZip.readEntry(src, 'folder1/f1-1.xlsx')) instanceof Error)
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
it('P04d 無效filename時reject', async function() {
|
|
530
|
+
for (let name of [undefined, null, 1, '']) {
|
|
531
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fpZipTxt, name)), 'invalid filename')
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
it('P04e 無效opt.type時reject', async function() {
|
|
536
|
+
for (let type of ['buffer', null, 1]) {
|
|
537
|
+
assert.strict.equal(await getRejection(wz.mZip.readEntry(fpZipTxt, 'file1(中文).txt', { type })), 'invalid opt.type')
|
|
538
|
+
}
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
it('P04f 含不安全路徑之壓縮檔整個reject', async function() {
|
|
542
|
+
let err = await getRejection(wz.mZip.readEntry(fpZipSlip, 'ok.txt'))
|
|
543
|
+
assert.match(err.message, /Unsafe filename/)
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
it('P10a docx結構: document.xml等於zip.js直接讀出, word/media項數一致', async function() {
|
|
547
|
+
let zr = new ZipReader(new Uint8ArrayReader(fs.readFileSync(fpZipDocx)))
|
|
548
|
+
let es = await zr.getEntries()
|
|
549
|
+
let textDirect = await es.find((v) => v.filename === 'word/document.xml').getData(new TextWriter())
|
|
550
|
+
let nMediaDirect = es.filter((v) => v.filename.startsWith('word/media/') && !v.directory).length
|
|
551
|
+
await zr.close()
|
|
552
|
+
let text = await wz.mZip.readEntry(fpZipDocx, 'word/document.xml')
|
|
553
|
+
assert.strict.equal(text, textDirect)
|
|
554
|
+
assert.strict.equal(text, xmlDocx)
|
|
555
|
+
let nMedia = (await wz.mZip.listEntries(fpZipDocx)).filter((v) => v.filename.startsWith('word/media/') && !v.directory).length
|
|
556
|
+
assert.strict.equal(nMedia, nMediaDirect)
|
|
557
|
+
assert.strict.equal(nMedia, 2)
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
describe('resource', function() {
|
|
563
|
+
|
|
564
|
+
it('P07a 給予檔案路徑時只讀取部分內容, 20MB檔案讀取量小於1MB', async function() {
|
|
565
|
+
assert.ok(fs.statSync(fpZipBig).size > 20 * 1024 * 1024)
|
|
566
|
+
let sl = await spyFs(() => wz.mZip.listEntries(fpZipBig))
|
|
567
|
+
assert.strict.deepEqual(sl.result.map((v) => v.filename), ['big.bin', 'small.txt'])
|
|
568
|
+
assert.strict.equal(sl.opened, 1) //確為經檔案代號讀取, 非整檔讀入
|
|
569
|
+
assert.ok(sl.bytesRead > 0 && sl.bytesRead < 1024 * 1024, `listEntries bytesRead ${sl.bytesRead}`)
|
|
570
|
+
let sr = await spyFs(() => wz.mZip.readEntry(fpZipBig, 'small.txt'))
|
|
571
|
+
assert.strict.equal(sr.result, 'small')
|
|
572
|
+
assert.strict.equal(sr.opened, 1)
|
|
573
|
+
assert.ok(sr.bytesRead > 0 && sr.bytesRead < 1024 * 1024, `readEntry bytesRead ${sr.bytesRead}`)
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
it('P08a 成功與各失敗路徑結束後檔案代號皆已關閉', async function() {
|
|
577
|
+
let cases = [
|
|
578
|
+
['listEntries成功', () => wz.mZip.listEntries(fpZipFolder), false],
|
|
579
|
+
['readEntry成功', () => wz.mZip.readEntry(fpZipFolder, 'folder1/f1-1.xlsx', { type: 'u8' }), false],
|
|
580
|
+
['找不到項目', () => wz.mZip.readEntry(fpZipFolder, 'nope.txt'), true],
|
|
581
|
+
['項目為資料夾', () => wz.mZip.readEntry(fpZipFolder, 'folder1/folder2/'), true],
|
|
582
|
+
['錯誤密碼', () => wz.mZip.readEntry(fpZipPW, 'folder1/f1-1.xlsx', { pw: 'abd' }), true],
|
|
583
|
+
['無密碼', () => wz.mZip.readEntry(fpZipPW, 'folder1/f1-1.xlsx'), true],
|
|
584
|
+
['非zip之listEntries', () => wz.mZip.listEntries(fpSrcTxt), true],
|
|
585
|
+
['非zip之readEntry', () => wz.mZip.readEntry(fpSrcTxt, 'a.txt'), true],
|
|
586
|
+
['不安全路徑', () => wz.mZip.listEntries(fpZipSlip), true],
|
|
587
|
+
]
|
|
588
|
+
for (let [name, fun, expectError] of cases) {
|
|
589
|
+
let st = await spyFs(fun)
|
|
590
|
+
assert.strict.equal(st.error !== undefined, expectError, `${name}: error ${st.error}`)
|
|
591
|
+
assert.strict.equal(st.opened, 1, `${name}: opened ${st.opened}`)
|
|
592
|
+
assert.strict.equal(st.closed, st.opened, `${name}: closed ${st.closed}`)
|
|
593
|
+
}
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
it('P08b 檔案讀取發生短讀時仍得完整結果', async function() {
|
|
597
|
+
let exp = fs.readFileSync(`${fdSrc}/f1-1.xlsx`)
|
|
598
|
+
let sr = await spyFs(() => wz.mZip.readEntry(fpZipFolder, 'folder1/f1-1.xlsx', { type: 'u8' }), { maxChunk: 7 })
|
|
599
|
+
assert.strict.equal(sr.error, undefined, String(sr.error))
|
|
600
|
+
assert.ok(Buffer.from(sr.result).equals(exp))
|
|
601
|
+
let sl = await spyFs(() => wz.mZip.listEntries(fpZipFolder), { maxChunk: 7 })
|
|
602
|
+
assert.strict.equal(sl.error, undefined, String(sl.error))
|
|
603
|
+
assert.strict.deepEqual(sl.result, await wz.mZip.listEntries(fpZipFolder))
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
it('P08a 前置檢查失敗時不開啟檔案', async function() {
|
|
607
|
+
let cases = [
|
|
608
|
+
() => wz.mZip.readEntry(fpZipFolder, ''),
|
|
609
|
+
() => wz.mZip.readEntry(fpZipFolder, 'a.txt', { type: 'buffer' }),
|
|
610
|
+
() => wz.mZip.readEntry(fpZipFolder, 'a.txt', { pw: 123 }),
|
|
611
|
+
() => wz.mZip.listEntries(fdTmp),
|
|
612
|
+
]
|
|
613
|
+
for (let fun of cases) {
|
|
614
|
+
let st = await spyFs(fun)
|
|
615
|
+
assert.ok(st.error !== undefined)
|
|
616
|
+
assert.strict.equal(st.opened, 0)
|
|
617
|
+
}
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
it('P09a 同一檔案並發讀取5次結果一致且皆關閉', async function() {
|
|
621
|
+
let exp = fs.readFileSync(`${fdSrc}/f1-1.xlsx`)
|
|
622
|
+
let st = await spyFs(() => Promise.all([1, 2, 3, 4, 5].map(() => wz.mZip.readEntry(fpZipFolder, 'folder1/f1-1.xlsx', { type: 'u8' }))))
|
|
623
|
+
assert.strict.equal(st.result.length, 5)
|
|
624
|
+
assert.ok(st.result.every((r) => Buffer.from(r).equals(exp)))
|
|
625
|
+
assert.strict.equal(st.opened, 5)
|
|
626
|
+
assert.strict.equal(st.closed, 5)
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
it('P01c 讀取不寫入磁碟', async function() {
|
|
630
|
+
let before = listFiles(fdTmp)
|
|
631
|
+
await wz.mZip.listEntries(fpZipFolder)
|
|
632
|
+
await wz.mZip.readEntry(fpZipFolder, 'folder1/f1-1.xlsx', { type: 'u8' })
|
|
633
|
+
await wz.mZip.readEntry(fpZipTxt, 'file1(中文).txt')
|
|
634
|
+
assert.strict.deepEqual(listFiles(fdTmp), before)
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
})
|
|
638
|
+
|
|
639
|
+
})
|