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
package/docs/mZip.mjs.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="m7z.html">m7z</a><ul class='methods'><li data-type='method'><a href="m7z.html#.setProg">setProg</a></li><li data-type='method'><a href="m7z.html#.unzip">unzip</a></li><li data-type='method'><a href="m7z.html#.zipFile">zipFile</a></li><li data-type='method'><a href="m7z.html#.zipFolder">zipFolder</a></li></ul></li><li><a href="mZip.html">mZip</a><ul class='methods'><li data-type='method'><a href="mZip.html#.unzip">unzip</a></li><li data-type='method'><a href="mZip.html#.zipFile">zipFile</a></li><li data-type='method'><a href="mZip.html#.zipFolder">zipFolder</a></li></ul></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="m7z.html">m7z</a><ul class='methods'><li data-type='method'><a href="m7z.html#.setProg">setProg</a></li><li data-type='method'><a href="m7z.html#.unzip">unzip</a></li><li data-type='method'><a href="m7z.html#.zipFile">zipFile</a></li><li data-type='method'><a href="m7z.html#.zipFolder">zipFolder</a></li></ul></li><li><a href="mZip.html">mZip</a><ul class='methods'><li data-type='method'><a href="mZip.html#.listEntries">listEntries</a></li><li data-type='method'><a href="mZip.html#.readEntry">readEntry</a></li><li data-type='method'><a href="mZip.html#.unzip">unzip</a></li><li data-type='method'><a href="mZip.html#.zipFile">zipFile</a></li><li data-type='method'><a href="mZip.html#.zipFolder">zipFolder</a></li></ul></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<article>
|
|
48
48
|
<pre class="prettyprint source linenums"><code>import path from 'path'
|
|
49
49
|
import fs from 'fs'
|
|
50
|
-
import { configure, ZipWriter, ZipReader, Uint8ArrayReader, Uint8ArrayWriter } from '@zip.js/zip.js'
|
|
50
|
+
import { configure, ZipWriter, ZipReader, Reader, Uint8ArrayReader, Uint8ArrayWriter, TextWriter } from '@zip.js/zip.js'
|
|
51
51
|
import get from 'lodash-es/get.js'
|
|
52
52
|
import fsTreeFolder from 'wsemi/src/fsTreeFolder.mjs'
|
|
53
53
|
import getFileName from 'wsemi/src/getFileName.mjs'
|
|
@@ -62,7 +62,7 @@ configure({ useWebWorkers: false }) //關閉web worker改用主執行緒壓縮,
|
|
|
62
62
|
* Zip處理
|
|
63
63
|
*
|
|
64
64
|
* @class
|
|
65
|
-
* @returns {Object} 回傳壓縮物件,可使用函數zipFile、zipFolder、unzip
|
|
65
|
+
* @returns {Object} 回傳壓縮物件,可使用函數zipFile、zipFolder、unzip、listEntries、readEntry
|
|
66
66
|
* @example
|
|
67
67
|
* import wz from 'w-zip'
|
|
68
68
|
*
|
|
@@ -109,6 +109,21 @@ configure({ useWebWorkers: false }) //關閉web worker改用主執行緒壓縮,
|
|
|
109
109
|
* console.log('unzip2 with password', await wz.mZip.unzip(fpZip2PW, fpUnzipExtract + '/test2PW', { pw }))
|
|
110
110
|
* console.log('unzip2 with password after')
|
|
111
111
|
*
|
|
112
|
+
* //listEntries
|
|
113
|
+
* console.log('listEntries before')
|
|
114
|
+
* console.log('listEntries', (await wz.mZip.listEntries(fpZip2)).map((v) => v.filename))
|
|
115
|
+
* console.log('listEntries after')
|
|
116
|
+
*
|
|
117
|
+
* //readEntry
|
|
118
|
+
* console.log('readEntry before')
|
|
119
|
+
* console.log('readEntry', JSON.parse(await wz.mZip.readEntry(fpZip1, 'file1(中文).txt')).name)
|
|
120
|
+
* console.log('readEntry after')
|
|
121
|
+
*
|
|
122
|
+
* //readEntry with password
|
|
123
|
+
* console.log('readEntry with password before')
|
|
124
|
+
* console.log('readEntry with password', (await wz.mZip.readEntry(fpZip2PW, 'folder1/f1-1.xlsx', { type: 'u8', pw })).length)
|
|
125
|
+
* console.log('readEntry with password after')
|
|
126
|
+
*
|
|
112
127
|
* }
|
|
113
128
|
* test()
|
|
114
129
|
* .catch((err) => {
|
|
@@ -133,6 +148,22 @@ configure({ useWebWorkers: false }) //關閉web worker改用主執行緒壓縮,
|
|
|
133
148
|
* // unzip2 with password before
|
|
134
149
|
* // unzip2 with password done: test2PW
|
|
135
150
|
* // unzip2 with password after
|
|
151
|
+
* // listEntries before
|
|
152
|
+
* // listEntries [
|
|
153
|
+
* // 'folder1/f1-1.xlsx',
|
|
154
|
+
* // 'folder1/f1-2.xlsx',
|
|
155
|
+
* // 'folder1/folder2/',
|
|
156
|
+
* // 'folder1/folder2/f2-1.xlsx',
|
|
157
|
+
* // 'folder1/folder2/f2-2.xlsx',
|
|
158
|
+
* // 'folder1/folder2/f2-3(中文).xlsx'
|
|
159
|
+
* // ]
|
|
160
|
+
* // listEntries after
|
|
161
|
+
* // readEntry before
|
|
162
|
+
* // readEntry w-backup
|
|
163
|
+
* // readEntry after
|
|
164
|
+
* // readEntry with password before
|
|
165
|
+
* // readEntry with password 6663
|
|
166
|
+
* // readEntry with password after
|
|
136
167
|
*/
|
|
137
168
|
function mZip() {
|
|
138
169
|
|
|
@@ -375,10 +406,219 @@ function mZip() {
|
|
|
375
406
|
}
|
|
376
407
|
|
|
377
408
|
|
|
409
|
+
//checkSrc, 檢查不解壓讀取之來源, 可為檔案位置字串或Buffer、Uint8Array, 有誤回傳錯誤訊息字串, 無誤回傳空字串
|
|
410
|
+
function checkSrc(src) {
|
|
411
|
+
|
|
412
|
+
//Uint8Array, Buffer亦為Uint8Array
|
|
413
|
+
if (src instanceof Uint8Array) {
|
|
414
|
+
return ''
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
//check
|
|
418
|
+
if (typeof src !== 'string') {
|
|
419
|
+
return 'invalid src'
|
|
420
|
+
}
|
|
421
|
+
if (!fs.existsSync(src)) {
|
|
422
|
+
return 'invalid path of source file'
|
|
423
|
+
}
|
|
424
|
+
if (!fs.lstatSync(src).isFile()) {
|
|
425
|
+
return 'path of source is not file'
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return ''
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
//readZip, 開啟src之ZipReader交由fun處理, 結束時(含失敗)關閉ZipReader與檔案代號
|
|
433
|
+
async function readZip(src, readerOpt, fun) {
|
|
434
|
+
|
|
435
|
+
//reader
|
|
436
|
+
let fh = null
|
|
437
|
+
let reader
|
|
438
|
+
if (src instanceof Uint8Array) {
|
|
439
|
+
reader = new Uint8ArrayReader(src)
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
|
|
443
|
+
//reader, 以zip.js之Reader為基底(沿用其createReadable)補上init與readUint8Array, 依位置隨機讀取檔案, 只讀中央目錄與所需項目而不載入整檔
|
|
444
|
+
reader = new Reader()
|
|
445
|
+
reader.init = async () => {
|
|
446
|
+
fh = await fs.promises.open(src, 'r')
|
|
447
|
+
reader.size = (await fh.stat()).size
|
|
448
|
+
reader.initialized = true
|
|
449
|
+
}
|
|
450
|
+
reader.readUint8Array = async (index, length) => {
|
|
451
|
+
|
|
452
|
+
//len, 依檔案實際大小夾限, 避免依異常之宣告長度配置過大緩衝區(同Uint8ArrayReader以slice夾限)
|
|
453
|
+
let len = Math.max(0, Math.min(length, reader.size - index))
|
|
454
|
+
|
|
455
|
+
//read, 迴圈讀滿以處理短讀, 讀到0位元組視為檔尾
|
|
456
|
+
let u8 = new Uint8Array(len)
|
|
457
|
+
let n = 0
|
|
458
|
+
while (n < len) {
|
|
459
|
+
let { bytesRead } = await fh.read(u8, n, len - n, index + n)
|
|
460
|
+
if (bytesRead === 0) {
|
|
461
|
+
break
|
|
462
|
+
}
|
|
463
|
+
n += bytesRead
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return n < len ? u8.subarray(0, n) : u8
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
//zipReader, 項目名之'\'視為路徑分隔而正規化為'/'(壓縮檔規範要求以'/'分隔, 部分工具寫入'\')
|
|
472
|
+
let zipReader = new ZipReader(reader, Object.assign({}, readerOpt, {
|
|
473
|
+
normalizeFilename: (filename) => filename.replaceAll('\\', '/'),
|
|
474
|
+
}))
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
return await fun(zipReader)
|
|
478
|
+
}
|
|
479
|
+
finally {
|
|
480
|
+
try {
|
|
481
|
+
await zipReader.close()
|
|
482
|
+
}
|
|
483
|
+
finally {
|
|
484
|
+
if (fh !== null) {
|
|
485
|
+
await fh.close()
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* 列出壓縮檔內項目,不解壓至資料夾
|
|
494
|
+
*
|
|
495
|
+
* 加密之壓縮檔不需密碼即可列出。含不安全路徑(例如'../'、絕對路徑、磁碟代號)項目之壓縮檔會整個reject。
|
|
496
|
+
* 項目名以'\'分隔者會正規化為'/'。項目名未標示UTF-8且無Unicode路徑欄位者(例如部分工具以Big5寫入),依zip.js預設以CP437解碼,名稱會呈現亂碼,讀取時須使用回傳之名稱。
|
|
497
|
+
*
|
|
498
|
+
* @memberof mZip
|
|
499
|
+
* @param {String|Buffer|Uint8Array} src 輸入壓縮檔案位置字串,或壓縮檔內容之Buffer、Uint8Array,給予檔案位置時只讀取中央目錄而不載入整個檔案
|
|
500
|
+
* @returns {Promise} 回傳Promise,resolve為項目陣列,依壓縮檔中央目錄順序,各項目為{filename,directory,uncompressedSize,compressedSize,encrypted,lastModDate},filename為壓縮檔內以'/'分隔之路徑,reject為失敗資訊
|
|
501
|
+
*/
|
|
502
|
+
async function listEntries(src) {
|
|
503
|
+
|
|
504
|
+
//check
|
|
505
|
+
let errSrc = checkSrc(src)
|
|
506
|
+
if (errSrc !== '') {
|
|
507
|
+
return Promise.reject(errSrc)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
|
|
512
|
+
//entries, 轉為純物件, 不外露zip.js之entry(讀取器關閉後其getData已不可用)
|
|
513
|
+
let entries = await readZip(src, {}, async (zipReader) => {
|
|
514
|
+
let es = await zipReader.getEntries()
|
|
515
|
+
return es.map((entry) => {
|
|
516
|
+
return {
|
|
517
|
+
filename: entry.filename,
|
|
518
|
+
directory: entry.directory,
|
|
519
|
+
uncompressedSize: entry.uncompressedSize,
|
|
520
|
+
compressedSize: entry.compressedSize,
|
|
521
|
+
encrypted: entry.encrypted,
|
|
522
|
+
lastModDate: entry.lastModDate,
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
return entries
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
return Promise.reject(err)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* 讀取壓縮檔內單一項目,不解壓至資料夾
|
|
537
|
+
*
|
|
538
|
+
* 讀出內容會驗證CRC32,資料損毀或密碼錯誤時reject。含不安全路徑(例如'../'、絕對路徑、磁碟代號)項目之壓縮檔會整個reject。
|
|
539
|
+
*
|
|
540
|
+
* @memberof mZip
|
|
541
|
+
* @param {String|Buffer|Uint8Array} src 輸入壓縮檔案位置字串,或壓縮檔內容之Buffer、Uint8Array,給予檔案位置時只讀取中央目錄與該項目而不載入整個檔案
|
|
542
|
+
* @param {String} filename 輸入項目路徑字串,須與listEntries回傳之filename完全相同(以'/'分隔且區分大小寫),若有同名項目則取最後一筆
|
|
543
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
544
|
+
* @param {String} [opt.type='text'] 輸入回傳資料型態字串,可選'text'為UTF-8解碼之字串(去除BOM)、'u8'為Uint8Array,非UTF-8之文字內容請用'u8'自行解碼,預設'text'
|
|
545
|
+
* @param {String} [opt.pw=''] 輸入解壓縮密碼字串,預設''
|
|
546
|
+
* @returns {Promise} 回傳Promise,resolve為項目內容之字串或Uint8Array,reject為失敗資訊,找不到項目或項目為資料夾時亦為reject
|
|
547
|
+
*/
|
|
548
|
+
async function readEntry(src, filename, opt = {}) {
|
|
549
|
+
|
|
550
|
+
//check
|
|
551
|
+
let errSrc = checkSrc(src)
|
|
552
|
+
if (errSrc !== '') {
|
|
553
|
+
return Promise.reject(errSrc)
|
|
554
|
+
}
|
|
555
|
+
if (typeof filename !== 'string' || filename === '') {
|
|
556
|
+
return Promise.reject('invalid filename')
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
//default
|
|
560
|
+
let type = get(opt, 'type', 'text')
|
|
561
|
+
let pw = get(opt, 'pw', '')
|
|
562
|
+
|
|
563
|
+
//check type
|
|
564
|
+
if (type !== 'text' && type !== 'u8') {
|
|
565
|
+
return Promise.reject('invalid opt.type')
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
//check pw
|
|
569
|
+
if (typeof pw !== 'string') {
|
|
570
|
+
return Promise.reject('invalid opt.pw')
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
|
|
575
|
+
//readerOpt, 驗證CRC32以免資料損毀或錯誤密碼(ZipCrypto僅以1位元組驗證密碼)時回傳錯誤內容
|
|
576
|
+
let readerOpt = { checkCrc32: true }
|
|
577
|
+
if (pw !== '') {
|
|
578
|
+
readerOpt.password = pw
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
//data
|
|
582
|
+
let data = await readZip(src, readerOpt, async (zipReader) => {
|
|
583
|
+
|
|
584
|
+
//entry, 同名取最後一筆, 同JSZip、node-stream-zip、Python zipfile之慣例
|
|
585
|
+
let entry = null
|
|
586
|
+
let entries = await zipReader.getEntries()
|
|
587
|
+
for (let e of entries) {
|
|
588
|
+
if (e.filename === filename) {
|
|
589
|
+
entry = e
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
//check
|
|
594
|
+
if (entry === null) {
|
|
595
|
+
return Promise.reject(`entry not found: ${filename}`)
|
|
596
|
+
}
|
|
597
|
+
if (entry.directory) {
|
|
598
|
+
return Promise.reject(`entry is directory: ${filename}`)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
//getData, text同zip.js之TextWriter(UTF-8且去除BOM)
|
|
602
|
+
if (type === 'u8') {
|
|
603
|
+
return entry.getData(new Uint8ArrayWriter())
|
|
604
|
+
}
|
|
605
|
+
return entry.getData(new TextWriter())
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
return data
|
|
609
|
+
}
|
|
610
|
+
catch (err) {
|
|
611
|
+
return Promise.reject(err)
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
|
|
378
616
|
return {
|
|
379
617
|
zipFile,
|
|
380
618
|
zipFolder,
|
|
381
619
|
unzip,
|
|
620
|
+
listEntries,
|
|
621
|
+
readEntry,
|
|
382
622
|
}
|
|
383
623
|
}
|
|
384
624
|
|
|
@@ -398,7 +638,7 @@ export default mZip()
|
|
|
398
638
|
<br class="clear">
|
|
399
639
|
|
|
400
640
|
<footer>
|
|
401
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
641
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 02 2026 15:37:30 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
402
642
|
</footer>
|
|
403
643
|
|
|
404
644
|
<script>prettyPrint();</script>
|
package/{g-7z.mjs → g_7z.mjs}
RENAMED
package/{g-zip.mjs → g_zip.mjs}
RENAMED
|
@@ -45,6 +45,18 @@ async function test() {
|
|
|
45
45
|
console.log('unzip2 with password')
|
|
46
46
|
console.log(await wz.mZip.unzip(fpZip2PW, fpUnzip2PW, { pw }))
|
|
47
47
|
|
|
48
|
+
//listEntries
|
|
49
|
+
console.log('listEntries')
|
|
50
|
+
console.log((await wz.mZip.listEntries(fpZip2)).map((v) => v.filename))
|
|
51
|
+
|
|
52
|
+
//readEntry
|
|
53
|
+
console.log('readEntry')
|
|
54
|
+
console.log(JSON.parse(await wz.mZip.readEntry(fpZip1, 'file1(中文).txt')).name)
|
|
55
|
+
|
|
56
|
+
//readEntry with password
|
|
57
|
+
console.log('readEntry with password')
|
|
58
|
+
console.log((await wz.mZip.readEntry(fpZip2PW, 'folder1/f1-1.xlsx', { type: 'u8', pw })).length)
|
|
59
|
+
|
|
48
60
|
console.log('finish')
|
|
49
61
|
}
|
|
50
62
|
test()
|
|
@@ -52,4 +64,4 @@ test()
|
|
|
52
64
|
console.log(err)
|
|
53
65
|
})
|
|
54
66
|
|
|
55
|
-
//node
|
|
67
|
+
//node g_zip.mjs
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-zip",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.43",
|
|
4
4
|
"main": "dist/w-zip.umd.js",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@zip.js/zip.js": "^2.
|
|
7
|
-
"wsemi": "^1.9.
|
|
6
|
+
"@zip.js/zip.js": "^2.22.0",
|
|
7
|
+
"wsemi": "^1.9.9"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"w-package-tools": "^1.1.16"
|
package/src/mZip.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import fs from 'fs'
|
|
3
|
-
import { configure, ZipWriter, ZipReader, Uint8ArrayReader, Uint8ArrayWriter } from '@zip.js/zip.js'
|
|
3
|
+
import { configure, ZipWriter, ZipReader, Reader, Uint8ArrayReader, Uint8ArrayWriter, TextWriter } from '@zip.js/zip.js'
|
|
4
4
|
import get from 'lodash-es/get.js'
|
|
5
5
|
import fsTreeFolder from 'wsemi/src/fsTreeFolder.mjs'
|
|
6
6
|
import getFileName from 'wsemi/src/getFileName.mjs'
|
|
@@ -15,7 +15,7 @@ configure({ useWebWorkers: false }) //關閉web worker改用主執行緒壓縮,
|
|
|
15
15
|
* Zip處理
|
|
16
16
|
*
|
|
17
17
|
* @class
|
|
18
|
-
* @returns {Object} 回傳壓縮物件,可使用函數zipFile、zipFolder、unzip
|
|
18
|
+
* @returns {Object} 回傳壓縮物件,可使用函數zipFile、zipFolder、unzip、listEntries、readEntry
|
|
19
19
|
* @example
|
|
20
20
|
* import wz from 'w-zip'
|
|
21
21
|
*
|
|
@@ -62,6 +62,21 @@ configure({ useWebWorkers: false }) //關閉web worker改用主執行緒壓縮,
|
|
|
62
62
|
* console.log('unzip2 with password', await wz.mZip.unzip(fpZip2PW, fpUnzipExtract + '/test2PW', { pw }))
|
|
63
63
|
* console.log('unzip2 with password after')
|
|
64
64
|
*
|
|
65
|
+
* //listEntries
|
|
66
|
+
* console.log('listEntries before')
|
|
67
|
+
* console.log('listEntries', (await wz.mZip.listEntries(fpZip2)).map((v) => v.filename))
|
|
68
|
+
* console.log('listEntries after')
|
|
69
|
+
*
|
|
70
|
+
* //readEntry
|
|
71
|
+
* console.log('readEntry before')
|
|
72
|
+
* console.log('readEntry', JSON.parse(await wz.mZip.readEntry(fpZip1, 'file1(中文).txt')).name)
|
|
73
|
+
* console.log('readEntry after')
|
|
74
|
+
*
|
|
75
|
+
* //readEntry with password
|
|
76
|
+
* console.log('readEntry with password before')
|
|
77
|
+
* console.log('readEntry with password', (await wz.mZip.readEntry(fpZip2PW, 'folder1/f1-1.xlsx', { type: 'u8', pw })).length)
|
|
78
|
+
* console.log('readEntry with password after')
|
|
79
|
+
*
|
|
65
80
|
* }
|
|
66
81
|
* test()
|
|
67
82
|
* .catch((err) => {
|
|
@@ -86,6 +101,22 @@ configure({ useWebWorkers: false }) //關閉web worker改用主執行緒壓縮,
|
|
|
86
101
|
* // unzip2 with password before
|
|
87
102
|
* // unzip2 with password done: test2PW
|
|
88
103
|
* // unzip2 with password after
|
|
104
|
+
* // listEntries before
|
|
105
|
+
* // listEntries [
|
|
106
|
+
* // 'folder1/f1-1.xlsx',
|
|
107
|
+
* // 'folder1/f1-2.xlsx',
|
|
108
|
+
* // 'folder1/folder2/',
|
|
109
|
+
* // 'folder1/folder2/f2-1.xlsx',
|
|
110
|
+
* // 'folder1/folder2/f2-2.xlsx',
|
|
111
|
+
* // 'folder1/folder2/f2-3(中文).xlsx'
|
|
112
|
+
* // ]
|
|
113
|
+
* // listEntries after
|
|
114
|
+
* // readEntry before
|
|
115
|
+
* // readEntry w-backup
|
|
116
|
+
* // readEntry after
|
|
117
|
+
* // readEntry with password before
|
|
118
|
+
* // readEntry with password 6663
|
|
119
|
+
* // readEntry with password after
|
|
89
120
|
*/
|
|
90
121
|
function mZip() {
|
|
91
122
|
|
|
@@ -328,10 +359,219 @@ function mZip() {
|
|
|
328
359
|
}
|
|
329
360
|
|
|
330
361
|
|
|
362
|
+
//checkSrc, 檢查不解壓讀取之來源, 可為檔案位置字串或Buffer、Uint8Array, 有誤回傳錯誤訊息字串, 無誤回傳空字串
|
|
363
|
+
function checkSrc(src) {
|
|
364
|
+
|
|
365
|
+
//Uint8Array, Buffer亦為Uint8Array
|
|
366
|
+
if (src instanceof Uint8Array) {
|
|
367
|
+
return ''
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
//check
|
|
371
|
+
if (typeof src !== 'string') {
|
|
372
|
+
return 'invalid src'
|
|
373
|
+
}
|
|
374
|
+
if (!fs.existsSync(src)) {
|
|
375
|
+
return 'invalid path of source file'
|
|
376
|
+
}
|
|
377
|
+
if (!fs.lstatSync(src).isFile()) {
|
|
378
|
+
return 'path of source is not file'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return ''
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
//readZip, 開啟src之ZipReader交由fun處理, 結束時(含失敗)關閉ZipReader與檔案代號
|
|
386
|
+
async function readZip(src, readerOpt, fun) {
|
|
387
|
+
|
|
388
|
+
//reader
|
|
389
|
+
let fh = null
|
|
390
|
+
let reader
|
|
391
|
+
if (src instanceof Uint8Array) {
|
|
392
|
+
reader = new Uint8ArrayReader(src)
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
|
|
396
|
+
//reader, 以zip.js之Reader為基底(沿用其createReadable)補上init與readUint8Array, 依位置隨機讀取檔案, 只讀中央目錄與所需項目而不載入整檔
|
|
397
|
+
reader = new Reader()
|
|
398
|
+
reader.init = async () => {
|
|
399
|
+
fh = await fs.promises.open(src, 'r')
|
|
400
|
+
reader.size = (await fh.stat()).size
|
|
401
|
+
reader.initialized = true
|
|
402
|
+
}
|
|
403
|
+
reader.readUint8Array = async (index, length) => {
|
|
404
|
+
|
|
405
|
+
//len, 依檔案實際大小夾限, 避免依異常之宣告長度配置過大緩衝區(同Uint8ArrayReader以slice夾限)
|
|
406
|
+
let len = Math.max(0, Math.min(length, reader.size - index))
|
|
407
|
+
|
|
408
|
+
//read, 迴圈讀滿以處理短讀, 讀到0位元組視為檔尾
|
|
409
|
+
let u8 = new Uint8Array(len)
|
|
410
|
+
let n = 0
|
|
411
|
+
while (n < len) {
|
|
412
|
+
let { bytesRead } = await fh.read(u8, n, len - n, index + n)
|
|
413
|
+
if (bytesRead === 0) {
|
|
414
|
+
break
|
|
415
|
+
}
|
|
416
|
+
n += bytesRead
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return n < len ? u8.subarray(0, n) : u8
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
//zipReader, 項目名之'\'視為路徑分隔而正規化為'/'(壓縮檔規範要求以'/'分隔, 部分工具寫入'\')
|
|
425
|
+
let zipReader = new ZipReader(reader, Object.assign({}, readerOpt, {
|
|
426
|
+
normalizeFilename: (filename) => filename.replaceAll('\\', '/'),
|
|
427
|
+
}))
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
return await fun(zipReader)
|
|
431
|
+
}
|
|
432
|
+
finally {
|
|
433
|
+
try {
|
|
434
|
+
await zipReader.close()
|
|
435
|
+
}
|
|
436
|
+
finally {
|
|
437
|
+
if (fh !== null) {
|
|
438
|
+
await fh.close()
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* 列出壓縮檔內項目,不解壓至資料夾
|
|
447
|
+
*
|
|
448
|
+
* 加密之壓縮檔不需密碼即可列出。含不安全路徑(例如'../'、絕對路徑、磁碟代號)項目之壓縮檔會整個reject。
|
|
449
|
+
* 項目名以'\'分隔者會正規化為'/'。項目名未標示UTF-8且無Unicode路徑欄位者(例如部分工具以Big5寫入),依zip.js預設以CP437解碼,名稱會呈現亂碼,讀取時須使用回傳之名稱。
|
|
450
|
+
*
|
|
451
|
+
* @memberof mZip
|
|
452
|
+
* @param {String|Buffer|Uint8Array} src 輸入壓縮檔案位置字串,或壓縮檔內容之Buffer、Uint8Array,給予檔案位置時只讀取中央目錄而不載入整個檔案
|
|
453
|
+
* @returns {Promise} 回傳Promise,resolve為項目陣列,依壓縮檔中央目錄順序,各項目為{filename,directory,uncompressedSize,compressedSize,encrypted,lastModDate},filename為壓縮檔內以'/'分隔之路徑,reject為失敗資訊
|
|
454
|
+
*/
|
|
455
|
+
async function listEntries(src) {
|
|
456
|
+
|
|
457
|
+
//check
|
|
458
|
+
let errSrc = checkSrc(src)
|
|
459
|
+
if (errSrc !== '') {
|
|
460
|
+
return Promise.reject(errSrc)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
|
|
465
|
+
//entries, 轉為純物件, 不外露zip.js之entry(讀取器關閉後其getData已不可用)
|
|
466
|
+
let entries = await readZip(src, {}, async (zipReader) => {
|
|
467
|
+
let es = await zipReader.getEntries()
|
|
468
|
+
return es.map((entry) => {
|
|
469
|
+
return {
|
|
470
|
+
filename: entry.filename,
|
|
471
|
+
directory: entry.directory,
|
|
472
|
+
uncompressedSize: entry.uncompressedSize,
|
|
473
|
+
compressedSize: entry.compressedSize,
|
|
474
|
+
encrypted: entry.encrypted,
|
|
475
|
+
lastModDate: entry.lastModDate,
|
|
476
|
+
}
|
|
477
|
+
})
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
return entries
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
return Promise.reject(err)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* 讀取壓縮檔內單一項目,不解壓至資料夾
|
|
490
|
+
*
|
|
491
|
+
* 讀出內容會驗證CRC32,資料損毀或密碼錯誤時reject。含不安全路徑(例如'../'、絕對路徑、磁碟代號)項目之壓縮檔會整個reject。
|
|
492
|
+
*
|
|
493
|
+
* @memberof mZip
|
|
494
|
+
* @param {String|Buffer|Uint8Array} src 輸入壓縮檔案位置字串,或壓縮檔內容之Buffer、Uint8Array,給予檔案位置時只讀取中央目錄與該項目而不載入整個檔案
|
|
495
|
+
* @param {String} filename 輸入項目路徑字串,須與listEntries回傳之filename完全相同(以'/'分隔且區分大小寫),若有同名項目則取最後一筆
|
|
496
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
497
|
+
* @param {String} [opt.type='text'] 輸入回傳資料型態字串,可選'text'為UTF-8解碼之字串(去除BOM)、'u8'為Uint8Array,非UTF-8之文字內容請用'u8'自行解碼,預設'text'
|
|
498
|
+
* @param {String} [opt.pw=''] 輸入解壓縮密碼字串,預設''
|
|
499
|
+
* @returns {Promise} 回傳Promise,resolve為項目內容之字串或Uint8Array,reject為失敗資訊,找不到項目或項目為資料夾時亦為reject
|
|
500
|
+
*/
|
|
501
|
+
async function readEntry(src, filename, opt = {}) {
|
|
502
|
+
|
|
503
|
+
//check
|
|
504
|
+
let errSrc = checkSrc(src)
|
|
505
|
+
if (errSrc !== '') {
|
|
506
|
+
return Promise.reject(errSrc)
|
|
507
|
+
}
|
|
508
|
+
if (typeof filename !== 'string' || filename === '') {
|
|
509
|
+
return Promise.reject('invalid filename')
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
//default
|
|
513
|
+
let type = get(opt, 'type', 'text')
|
|
514
|
+
let pw = get(opt, 'pw', '')
|
|
515
|
+
|
|
516
|
+
//check type
|
|
517
|
+
if (type !== 'text' && type !== 'u8') {
|
|
518
|
+
return Promise.reject('invalid opt.type')
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
//check pw
|
|
522
|
+
if (typeof pw !== 'string') {
|
|
523
|
+
return Promise.reject('invalid opt.pw')
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
|
|
528
|
+
//readerOpt, 驗證CRC32以免資料損毀或錯誤密碼(ZipCrypto僅以1位元組驗證密碼)時回傳錯誤內容
|
|
529
|
+
let readerOpt = { checkCrc32: true }
|
|
530
|
+
if (pw !== '') {
|
|
531
|
+
readerOpt.password = pw
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
//data
|
|
535
|
+
let data = await readZip(src, readerOpt, async (zipReader) => {
|
|
536
|
+
|
|
537
|
+
//entry, 同名取最後一筆, 同JSZip、node-stream-zip、Python zipfile之慣例
|
|
538
|
+
let entry = null
|
|
539
|
+
let entries = await zipReader.getEntries()
|
|
540
|
+
for (let e of entries) {
|
|
541
|
+
if (e.filename === filename) {
|
|
542
|
+
entry = e
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
//check
|
|
547
|
+
if (entry === null) {
|
|
548
|
+
return Promise.reject(`entry not found: ${filename}`)
|
|
549
|
+
}
|
|
550
|
+
if (entry.directory) {
|
|
551
|
+
return Promise.reject(`entry is directory: ${filename}`)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
//getData, text同zip.js之TextWriter(UTF-8且去除BOM)
|
|
555
|
+
if (type === 'u8') {
|
|
556
|
+
return entry.getData(new Uint8ArrayWriter())
|
|
557
|
+
}
|
|
558
|
+
return entry.getData(new TextWriter())
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
return data
|
|
562
|
+
}
|
|
563
|
+
catch (err) {
|
|
564
|
+
return Promise.reject(err)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
|
|
331
569
|
return {
|
|
332
570
|
zipFile,
|
|
333
571
|
zipFolder,
|
|
334
572
|
unzip,
|
|
573
|
+
listEntries,
|
|
574
|
+
readEntry,
|
|
335
575
|
}
|
|
336
576
|
}
|
|
337
577
|
|