From 6b40d37f9cde0b796517599498f5e940fa25b616 Mon Sep 17 00:00:00 2001 From: YFdyh000 Date: Thu, 16 Mar 2023 23:59:04 +0800 Subject: [PATCH 1/3] Allow interoperability to non-UTF-8 (bit 11 = 0) files --- .mocharc.yml | 1 + adm-zip.js | 11 +++- headers/entryHeader.js | 14 +++- package.json | 1 + test/mbcs/chs_name.zip | Bin 0 -> 223 bytes test/mbcs/test.js | 143 +++++++++++++++++++++++++++++++++++++++++ zipEntry.js | 6 ++ zipFile.js | 24 +++---- 8 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 test/mbcs/chs_name.zip create mode 100644 test/mbcs/test.js diff --git a/.mocharc.yml b/.mocharc.yml index 7ae2390..911eace 100644 --- a/.mocharc.yml +++ b/.mocharc.yml @@ -13,5 +13,6 @@ spec: - test/mocha.js - test/crc/index.js - test/multibyte/test.js + - test/mbcs/test.js - test/**/*.test.js - test/header.js diff --git a/adm-zip.js b/adm-zip.js index e1f1ce5..02afea1 100644 --- a/adm-zip.js +++ b/adm-zip.js @@ -67,7 +67,7 @@ module.exports = function (/**String*/ input, /** object */ options) { if (entry && _zip) { var item; // If entry was given as a file name - if (typeof entry === "string") item = _zip.getEntry(entry); + if (typeof entry === "string" || Buffer.isBuffer(entry)) item = _zip.getEntry(entry); // if entry was given as a ZipEntry object if (typeof entry === "object" && typeof entry.entryName !== "undefined" && typeof entry.header !== "undefined") item = _zip.getEntry(entry.entryName); @@ -417,7 +417,7 @@ module.exports = function (/**String*/ input, /** object */ options) { * If you want to create a directory the entryName must end in / and a null buffer should be provided. * Comment and attributes are optional * - * @param {string} entryName + * @param {Buffer | string} entryName * @param {Buffer | string} content - file content as buffer or utf8 coded string * @param {string} comment - file comment * @param {number | object} attr - number as unix file permissions, object as filesystem Stats object @@ -429,7 +429,12 @@ module.exports = function (/**String*/ input, /** object */ options) { // prepare new entry if (!update) { entry = new ZipEntry(); - entry.entryName = entryName; + if (!Buffer.isBuffer(entryName)) { + entry.entryName = entryName; + } else { + entry.rawEntryName = entryName; + entry.header.EFSflag = false; + } } entry.comment = comment || ""; diff --git a/headers/entryHeader.js b/headers/entryHeader.js index 572b9a7..897e058 100644 --- a/headers/entryHeader.js +++ b/headers/entryHeader.js @@ -63,6 +63,15 @@ module.exports = function () { _flags = val; }, + get EFSflag() { + return (_flags & Constants.FLG_EFS) == Constants.FLG_EFS; + }, + set EFSflag(newValue) { + if (this.EFSflag != newValue) { + _flags ^= Constants.FLG_EFS; + } + }, + get method() { return _method; }, @@ -181,6 +190,7 @@ module.exports = function () { if (data.readUInt32LE(0) !== Constants.LOCSIG) { throw new Error(Utils.Errors.INVALID_LOC); } + _flags = data.readUInt16LE(Constants.LOCFLG); _dataHeader = { // version needed to extract version: data.readUInt16LE(Constants.LOCVER), @@ -212,7 +222,7 @@ module.exports = function () { _verMade = data.readUInt16LE(Constants.CENVEM); // version needed to extract _version = data.readUInt16LE(Constants.CENVER); - // encrypt, decrypt flags + // bit 11 (EFS), bit 13 (encrypted Local Header) flags _flags = data.readUInt16LE(Constants.CENFLG); // compression method _method = data.readUInt16LE(Constants.CENHOW); @@ -275,7 +285,7 @@ module.exports = function () { data.writeUInt16LE(_verMade, Constants.CENVEM); // version needed to extract data.writeUInt16LE(_version, Constants.CENVER); - // encrypt, decrypt flags + // bit 11 (EFS), bit 13 (encrypted Local Header) flags data.writeUInt16LE(_flags, Constants.CENFLG); // compression method data.writeUInt16LE(_method, Constants.CENHOW); diff --git a/package.json b/package.json index ebc59ea..1bb053b 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ }, "devDependencies": { "chai": "^4.3.4", + "iconv-lite": "^0.6.3", "mocha": "^10.2.0", "prettier": "^2.2.1", "rimraf": "^3.0.2" diff --git a/test/mbcs/chs_name.zip b/test/mbcs/chs_name.zip new file mode 100644 index 0000000000000000000000000000000000000000..6409adb81a7a2e9f57242357bd5f285c3ae26371 GIT binary patch literal 223 zcmWIWW@Zs#U|`^22;#8~>(Yol$q(f5fUzXQwF~Et9NNBbn_fvpNoj#710$o+?batd z);^oo{$l(3r~6t!64hpF|DJr8>wi0H{<_ck^#jAE(Qg# tC?kUe1H-(?Q+CET8NK`DZh`2}HL_qDrz--yS=m6k8G*1INC$z90sshUMK1sV literal 0 HcmV?d00001 diff --git a/test/mbcs/test.js b/test/mbcs/test.js new file mode 100644 index 0000000..e2efd98 --- /dev/null +++ b/test/mbcs/test.js @@ -0,0 +1,143 @@ +const assert = require("assert"); +const path = require("path"); +const Zip = require("../../adm-zip"); +const rimraf = require("rimraf"); +const iconv = require('iconv-lite') + +describe("Multibyte Character Sets in Filename", () => { + const destination = __dirname + "/xxx"; + + before((done) => { + rimraf(destination, done) + } + ); + + it("ascii filename and Chinese content", (done) => { + let zip1 = new Zip(); + zip1.addFile('ascii.txt', '测试文本\ntest text'); + zip1.addFile('test/ascii.txt', '测试文本\ntest text'); + zip1.writeZip(path.join(destination, "00-ascii.zip")); + + let zip2 = new Zip(path.join(destination, "00-ascii.zip")); + let entry = zip2.getEntry('ascii.txt'); + let text = zip2.readFile(entry); + assert(text.toString() === '测试文本\ntest text', text.toString()); + done() + }); + + it("add files with chs filename into new zip", (done) => { + let zip1 = new Zip(); + zip1.addFile('中文路径.txt', '文件内容'); + zip1.addFile('test/中文路径.txt', '文件内容'); + zip1.writeZip(path.join(destination, "01-chs_name.zip")); + + let zip2 = new Zip(path.join(destination, "01-chs_name.zip")); + let entry = zip2.getEntry('中文路径.txt'); + let text = zip2.readFile(entry); + assert(text.toString() === '文件内容', text.toString()); + done() + }); + + it("fetch file with chs filename (gbk) in existing zip", (done) => { + let tZip = new Zip(path.join(__dirname, "chs_name.zip")); + for(let entry of tZip.getEntries()){ + if(entry.isDirectory) continue; + let CNpath = iconv.decode(entry.rawEntryName, 'gbk'); + assert(CNpath === '中文路径.txt') + } + done() + }); + + it("add file with chs filename into existing zip", (done) => { + let zip1 = new Zip(path.join(__dirname, "chs_name.zip")); + zip1.addFile('test/中文测试.txt', Buffer.from('文件内容')); + let entry = zip1.getEntry(iconv.encode('中文路径.txt','gbk')); + zip1.addFile('test/中文测试UTF-8.txt', Buffer.from('文件内容')); + zip1.writeZip(path.join(destination, "02-chs_name.zip")); + done() + }); + + it("read and keep entry.extra while write zip", () => { + let zip1 = new Zip(path.join(__dirname, "chs_name.zip")); + let entry1 = zip1.getEntry(iconv.encode('中文路径.txt','gbk')); + zip1.writeZip(path.join(destination, "03-chs_name_clone.zip")) + + let zip2 = new Zip(path.join(destination, "03-chs_name_clone.zip")); + let entry2 = zip2.getEntry(iconv.encode('中文路径.txt','gbk')); + assert(entry1.extra.equals(entry2.extra)); + + // "read EFSflag" + assert(entry1.header.EFSflag === false); + assert(entry2.header.EFSflag === false); + }); + + it("add files with cht filename (UTF-8) into new zip", (done) => { + let zip1 = new Zip(); + zip1.addFile('測試.txt', '測試'); + zip1.addFile('test/測試.txt', '測試'); + zip1.writeZip(path.join(destination, "04-cht_name.zip")); + + let zip2 = new Zip(path.join(destination, "04-cht_name.zip")); + let entry = zip2.getEntry('測試.txt'); + let text = zip2.readFile(entry); + assert(text.toString() === '測試', text.toString()); + + assert(entry.header.EFSflag); + done() + }); + it("add files with cht filename (Big5) into new zip", (done) => { + let zip1 = new Zip(); + zip1.addFile(iconv.encode('測試.txt','big5'), iconv.encode('測試','big5')); + zip1.addFile(iconv.encode('test/測試.txt','big5'), iconv.encode('測試','big5')); + zip1.writeZip(path.join(destination, "05-cht_name_big5.zip")); + + let zip2 = new Zip(path.join(destination, "05-cht_name_big5.zip")); + let entry = zip2.getEntry(iconv.encode('測試.txt','big5')); + let text = zip2.readFile(entry); + //console.log(entry.toJSON()) + assert(text.equals(iconv.encode('測試','big5'))); + + assert(!entry.header.EFSflag); + done() + }); + + it("add files with jp filename (UTF-8) into new zip", (done) => { + let zip1 = new Zip(); + zip1.addFile('にほんご.txt', 'にほんご'); + zip1.addFile('test/にほんご.txt', 'にほんご'); + zip1.writeZip(path.join(destination, "06-jp_name.zip")); + + let zip2 = new Zip(path.join(destination, "06-jp_name.zip")); + let entry = zip2.getEntry('にほんご.txt'); + let text = zip2.readFile(entry); + assert(text.toString() === 'にほんご', text.toString()); + done() + }); + it("add files with jp filename (EUC-JP) into new zip", (done) => { + let zip1 = new Zip(); + zip1.addFile(iconv.encode('にほんご.txt','EUC-JP'), iconv.encode('にほんご','EUC-JP')); + zip1.addFile(iconv.encode('test/にほんご.txt','EUC-JP'), iconv.encode('にほんご','EUC-JP')); + zip1.writeZip(path.join(destination, "07-jp_name.zip")); + + let zip2 = new Zip(path.join(destination, "07-jp_name.zip")); + let entry = zip2.getEntry(iconv.encode('にほんご.txt','EUC-JP')); + let text = zip2.readFile(entry); + //console.log(entry.toJSON()) + assert(text.equals(iconv.encode('にほんご','EUC-JP'))); + done() + }); + it("add files with jp filename (Shift_JIS) into new zip", (done) => { + let zip1 = new Zip(); + zip1.addFile(iconv.encode('にほんご.txt','Shift_JIS'), iconv.encode('にほんご','Shift_JIS')); + zip1.addFile(iconv.encode('test/にほんご.txt','Shift_JIS'), iconv.encode('にほんご','Shift_JIS')); + zip1.writeZip(path.join(destination, "08-jp_name.zip")); + + let zip2 = new Zip(path.join(destination, "08-jp_name.zip")); + let entry = zip2.getEntry(iconv.encode('にほんご.txt','Shift_JIS')); + let text = zip2.readFile(entry); + //console.log(entry.toJSON()) + assert(text.equals(iconv.encode('にほんご','Shift_JIS'))); + done() + }); + +}); diff --git a/zipEntry.js b/zipEntry.js index 8c3053b..9dc6b46 100644 --- a/zipEntry.js +++ b/zipEntry.js @@ -208,6 +208,12 @@ module.exports = function (/*Buffer*/ input) { _isDirectory = lastChar === 47 || lastChar === 92; _entryHeader.fileNameLength = _entryName.length; }, + set rawEntryName(val) { + _entryName = val; + var lastChar = _entryName[_entryName.length - 1]; + _isDirectory = lastChar === 47 || lastChar === 92; + _entryHeader.fileNameLength = _entryName.length; + }, get extra() { return _extra; diff --git a/zipFile.js b/zipFile.js index 9cf0729..c8b657e 100644 --- a/zipFile.js +++ b/zipFile.js @@ -49,7 +49,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { entry = new ZipEntry(inBuffer); entry.header = inBuffer.slice(tmp, (tmp += Utils.Constants.CENHDR)); - entry.entryName = inBuffer.slice(tmp, (tmp += entry.header.fileNameLength)); + entry.rawEntryName = inBuffer.slice(tmp, (tmp += entry.header.fileNameLength)); if (entry.header.extraLength) { entry.extra = inBuffer.slice(tmp, (tmp += entry.header.extraLength)); @@ -60,7 +60,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { index += entry.header.entryHeaderSize; entryList[i] = entry; - entryTable[entry.entryName] = entry; + entryTable[Utils.toBuffer(entry.rawEntryName)] = entry; } } @@ -164,7 +164,8 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { if (!loadedEntries) { readEntries(); } - return entryTable[entryName] || null; + let rawEntryName = Buffer.isBuffer(entryName) ? entryName : Utils.toBuffer(entryName); + return entryTable[rawEntryName] || null; }, /** @@ -177,7 +178,8 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { readEntries(); } entryList.push(entry); - entryTable[entry.entryName] = entry; + let rawEntryName = Buffer.isBuffer(entry) ? entry : Utils.toBuffer(entry.entryName); + entryTable[rawEntryName] = entry; mainHeader.totalEntries = entryList.length; }, @@ -191,17 +193,18 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { if (!loadedEntries) { readEntries(); } - var entry = entryTable[entryName]; + let rawEntryName = Buffer.isBuffer(entryName) ? entryName : Utils.toBuffer(entryName); + var entry = entryTable[rawEntryName]; if (entry && entry.isDirectory) { var _self = this; this.getEntryChildren(entry).forEach(function (child) { - if (child.entryName !== entryName) { - _self.deleteEntry(child.entryName); + if (child.rawEntryName !== entryName) { + _self.deleteEntry(child.rawEntryName); } }); } entryList.splice(entryList.indexOf(entry), 1); - delete entryTable[entryName]; + delete entryTable[rawEntryName]; mainHeader.totalEntries = entryList.length; }, @@ -217,11 +220,10 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { } if (entry && entry.isDirectory) { const list = []; - const name = entry.entryName; - const len = name.length; entryList.forEach(function (zipEntry) { - if (zipEntry.entryName.substr(0, len) === name) { + // NOTE: this does not consider the overlap of different codes + if (zipEntry.entryName === entry.entryName) { list.push(zipEntry); } }); From a399405390e227ce76a1c64b830268a85e962cc8 Mon Sep 17 00:00:00 2001 From: 5saviahv <5saviahv@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:28:34 +0300 Subject: [PATCH 2/3] updates --- test/mbcs/{test.js => mbcs.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/mbcs/{test.js => mbcs.test.js} (100%) diff --git a/test/mbcs/test.js b/test/mbcs/mbcs.test.js similarity index 100% rename from test/mbcs/test.js rename to test/mbcs/mbcs.test.js From 85e8305062016ef2d10cf1997a9e5a1f0e886cce Mon Sep 17 00:00:00 2001 From: 5saviahv <5saviahv@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:28:52 +0300 Subject: [PATCH 3/3] updates --- .mocharc.yml | 1 - adm-zip.js | 11 +- headers/entryHeader.js | 14 +- test/mbcs/mbcs.test.js | 387 ++++++++++++++++++++++++++++++----------- zipEntry.js | 6 - zipFile.js | 24 ++- 6 files changed, 299 insertions(+), 144 deletions(-) diff --git a/.mocharc.yml b/.mocharc.yml index 911eace..7ae2390 100644 --- a/.mocharc.yml +++ b/.mocharc.yml @@ -13,6 +13,5 @@ spec: - test/mocha.js - test/crc/index.js - test/multibyte/test.js - - test/mbcs/test.js - test/**/*.test.js - test/header.js diff --git a/adm-zip.js b/adm-zip.js index 02afea1..e1f1ce5 100644 --- a/adm-zip.js +++ b/adm-zip.js @@ -67,7 +67,7 @@ module.exports = function (/**String*/ input, /** object */ options) { if (entry && _zip) { var item; // If entry was given as a file name - if (typeof entry === "string" || Buffer.isBuffer(entry)) item = _zip.getEntry(entry); + if (typeof entry === "string") item = _zip.getEntry(entry); // if entry was given as a ZipEntry object if (typeof entry === "object" && typeof entry.entryName !== "undefined" && typeof entry.header !== "undefined") item = _zip.getEntry(entry.entryName); @@ -417,7 +417,7 @@ module.exports = function (/**String*/ input, /** object */ options) { * If you want to create a directory the entryName must end in / and a null buffer should be provided. * Comment and attributes are optional * - * @param {Buffer | string} entryName + * @param {string} entryName * @param {Buffer | string} content - file content as buffer or utf8 coded string * @param {string} comment - file comment * @param {number | object} attr - number as unix file permissions, object as filesystem Stats object @@ -429,12 +429,7 @@ module.exports = function (/**String*/ input, /** object */ options) { // prepare new entry if (!update) { entry = new ZipEntry(); - if (!Buffer.isBuffer(entryName)) { - entry.entryName = entryName; - } else { - entry.rawEntryName = entryName; - entry.header.EFSflag = false; - } + entry.entryName = entryName; } entry.comment = comment || ""; diff --git a/headers/entryHeader.js b/headers/entryHeader.js index 897e058..572b9a7 100644 --- a/headers/entryHeader.js +++ b/headers/entryHeader.js @@ -63,15 +63,6 @@ module.exports = function () { _flags = val; }, - get EFSflag() { - return (_flags & Constants.FLG_EFS) == Constants.FLG_EFS; - }, - set EFSflag(newValue) { - if (this.EFSflag != newValue) { - _flags ^= Constants.FLG_EFS; - } - }, - get method() { return _method; }, @@ -190,7 +181,6 @@ module.exports = function () { if (data.readUInt32LE(0) !== Constants.LOCSIG) { throw new Error(Utils.Errors.INVALID_LOC); } - _flags = data.readUInt16LE(Constants.LOCFLG); _dataHeader = { // version needed to extract version: data.readUInt16LE(Constants.LOCVER), @@ -222,7 +212,7 @@ module.exports = function () { _verMade = data.readUInt16LE(Constants.CENVEM); // version needed to extract _version = data.readUInt16LE(Constants.CENVER); - // bit 11 (EFS), bit 13 (encrypted Local Header) flags + // encrypt, decrypt flags _flags = data.readUInt16LE(Constants.CENFLG); // compression method _method = data.readUInt16LE(Constants.CENHOW); @@ -285,7 +275,7 @@ module.exports = function () { data.writeUInt16LE(_verMade, Constants.CENVEM); // version needed to extract data.writeUInt16LE(_version, Constants.CENVER); - // bit 11 (EFS), bit 13 (encrypted Local Header) flags + // encrypt, decrypt flags data.writeUInt16LE(_flags, Constants.CENFLG); // compression method data.writeUInt16LE(_method, Constants.CENHOW); diff --git a/test/mbcs/mbcs.test.js b/test/mbcs/mbcs.test.js index e2efd98..b94c6fa 100644 --- a/test/mbcs/mbcs.test.js +++ b/test/mbcs/mbcs.test.js @@ -1,143 +1,322 @@ const assert = require("assert"); -const path = require("path"); +const pth = require("path"); const Zip = require("../../adm-zip"); const rimraf = require("rimraf"); -const iconv = require('iconv-lite') +const iconv = require("iconv-lite"); describe("Multibyte Character Sets in Filename", () => { - const destination = __dirname + "/xxx"; + const destination = pth.resolve("./test/xxx"); + const asset1 = pth.resolve("./test/mbcs/", "chs_name.zip"); - before((done) => { - rimraf(destination, done) - } - ); + // clean up folder content + afterEach((done) => rimraf(destination, done)); - it("ascii filename and Chinese content", (done) => { - let zip1 = new Zip(); - zip1.addFile('ascii.txt', '测试文本\ntest text'); - zip1.addFile('test/ascii.txt', '测试文本\ntest text'); - zip1.writeZip(path.join(destination, "00-ascii.zip")); - - let zip2 = new Zip(path.join(destination, "00-ascii.zip")); - let entry = zip2.getEntry('ascii.txt'); - let text = zip2.readFile(entry); - assert(text.toString() === '测试文本\ntest text', text.toString()); - done() + // chinese + it("ascii filename and chinese content", (done) => { + const encoding = "ascii"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + const content = "测试文本\ntest text"; + + const zip1 = new Zip({ decoder }); + zip1.addFile("ascii.txt", content); + zip1.addFile("test/ascii.txt", content); + zip1.writeZip(pth.join(destination, "00-ascii.zip")); + + const zip2 = new Zip(pth.join(destination, "00-ascii.zip"), { decoder }); + const text = zip2.readAsText("ascii.txt"); + assert(text === content, text); + done(); }); - it("add files with chs filename into new zip", (done) => { - let zip1 = new Zip(); - zip1.addFile('中文路径.txt', '文件内容'); - zip1.addFile('test/中文路径.txt', '文件内容'); - zip1.writeZip(path.join(destination, "01-chs_name.zip")); - - let zip2 = new Zip(path.join(destination, "01-chs_name.zip")); - let entry = zip2.getEntry('中文路径.txt'); - let text = zip2.readFile(entry); - assert(text.toString() === '文件内容', text.toString()); - done() + it("add files with chinese filename into new zip", (done) => { + const encoding = "gbk"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + const content = "文件内容"; + const file = "中文路径.txt"; + + const zip1 = new Zip({ decoder }); + zip1.addFile(file, content); + zip1.addFile("test/" + file, content); + zip1.writeZip(pth.join(destination, "01-chs_name.zip")); + + const zip2 = new Zip(pth.join(destination, "01-chs_name.zip"), { decoder }); + const text = zip2.readAsText(file); + assert(text === content, text); + done(); }); - it("fetch file with chs filename (gbk) in existing zip", (done) => { - let tZip = new Zip(path.join(__dirname, "chs_name.zip")); - for(let entry of tZip.getEntries()){ - if(entry.isDirectory) continue; - let CNpath = iconv.decode(entry.rawEntryName, 'gbk'); - assert(CNpath === '中文路径.txt') + it("fetch file with chinese filename (gbk) in existing zip", (done) => { + const encoding = "gbk"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + let tZip = new Zip(asset1, { decoder }); + for (let entry of tZip.getEntries()) { + if (entry.isDirectory) continue; + const CNpath = entry.entryName; + assert(CNpath === "中文路径.txt"); } - done() + done(); }); - it("add file with chs filename into existing zip", (done) => { - let zip1 = new Zip(path.join(__dirname, "chs_name.zip")); - zip1.addFile('test/中文测试.txt', Buffer.from('文件内容')); - let entry = zip1.getEntry(iconv.encode('中文路径.txt','gbk')); - zip1.addFile('test/中文测试UTF-8.txt', Buffer.from('文件内容')); - zip1.writeZip(path.join(destination, "02-chs_name.zip")); - done() + it("add file with chinese filename into existing zip", (done) => { + const encoding = "gbk"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + const content = "文件内容"; + const file1 = "test/中文测试.txt"; + const file2 = "中文路径.txt"; + + let zip1 = new Zip(asset1, { decoder }); + zip1.addFile(file1, content); + zip1.writeZip(pth.join(destination, "02-chs_name.zip")); + + const zip2 = new Zip(pth.join(destination, "02-chs_name.zip"), { decoder }); + const text1 = zip2.readAsText(file1); + assert(text1 === content, text1); + + const text2 = zip2.readAsText(file2); + assert(text2 === content, text2); + + done(); }); it("read and keep entry.extra while write zip", () => { - let zip1 = new Zip(path.join(__dirname, "chs_name.zip")); - let entry1 = zip1.getEntry(iconv.encode('中文路径.txt','gbk')); - zip1.writeZip(path.join(destination, "03-chs_name_clone.zip")) + const encoding = "gbk"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + let zip1 = new Zip(asset1, { decoder }); + let entry1 = zip1.getEntry("中文路径.txt", "gbk"); + zip1.writeZip(pth.join(destination, "03-chs_name_clone.zip")); - let zip2 = new Zip(path.join(destination, "03-chs_name_clone.zip")); - let entry2 = zip2.getEntry(iconv.encode('中文路径.txt','gbk')); + let zip2 = new Zip(pth.join(destination, "03-chs_name_clone.zip"), { decoder }); + let entry2 = zip2.getEntry("中文路径.txt"); assert(entry1.extra.equals(entry2.extra)); // "read EFSflag" - assert(entry1.header.EFSflag === false); - assert(entry2.header.EFSflag === false); + assert(entry1.header.flags_efs === false); + assert(entry2.header.flags_efs === false); }); - it("add files with cht filename (UTF-8) into new zip", (done) => { + it("add files with chinese filename (UTF-8) into new zip", (done) => { let zip1 = new Zip(); - zip1.addFile('測試.txt', '測試'); - zip1.addFile('test/測試.txt', '測試'); - zip1.writeZip(path.join(destination, "04-cht_name.zip")); + zip1.addFile("測試.txt", "測試"); + zip1.addFile("test/測試.txt", "測試"); + zip1.writeZip(pth.join(destination, "04-cht_name.zip")); - let zip2 = new Zip(path.join(destination, "04-cht_name.zip")); - let entry = zip2.getEntry('測試.txt'); - let text = zip2.readFile(entry); - assert(text.toString() === '測試', text.toString()); + let zip2 = new Zip(pth.join(destination, "04-cht_name.zip")); + let entry = zip2.getEntry("測試.txt"); + const text = zip2.readAsText(entry); + assert(text === "測試", text); - assert(entry.header.EFSflag); - done() + assert(entry.header.flags_efs); + done(); }); - it("add files with cht filename (Big5) into new zip", (done) => { - let zip1 = new Zip(); - zip1.addFile(iconv.encode('測試.txt','big5'), iconv.encode('測試','big5')); - zip1.addFile(iconv.encode('test/測試.txt','big5'), iconv.encode('測試','big5')); - zip1.writeZip(path.join(destination, "05-cht_name_big5.zip")); - let zip2 = new Zip(path.join(destination, "05-cht_name_big5.zip")); - let entry = zip2.getEntry(iconv.encode('測試.txt','big5')); - let text = zip2.readFile(entry); + it("add files with chinese filename (Big5) into new zip", (done) => { + const encoding = "big5"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + const content = iconv.encode("測試", encoding); // buffer + + let zip1 = new Zip({ decoder }); + zip1.addFile("測試.txt", content); + zip1.addFile("test/測試.txt", content); + zip1.writeZip(pth.join(destination, "05-cht_name_big5.zip")); + + const zip2 = new Zip(pth.join(destination, "05-cht_name_big5.zip"), { decoder }); + const entry = zip2.getEntry("測試.txt"); + const bufdata = zip2.readFile(entry); //console.log(entry.toJSON()) - assert(text.equals(iconv.encode('測試','big5'))); + assert(bufdata.equals(content)); - assert(!entry.header.EFSflag); - done() + assert(!entry.header.flags_efs); + done(); }); - it("add files with jp filename (UTF-8) into new zip", (done) => { - let zip1 = new Zip(); - zip1.addFile('にほんご.txt', 'にほんご'); - zip1.addFile('test/にほんご.txt', 'にほんご'); - zip1.writeZip(path.join(destination, "06-jp_name.zip")); - - let zip2 = new Zip(path.join(destination, "06-jp_name.zip")); - let entry = zip2.getEntry('にほんご.txt'); - let text = zip2.readFile(entry); - assert(text.toString() === 'にほんご', text.toString()); - done() + // japanese + it("add files with japanese filename (UTF-8) into new zip", (done) => { + const file = "にほんご.txt"; + const content = "にほんご"; + + const zip1 = new Zip(); + zip1.addFile(file, content); + zip1.addFile("test/" + file, content); + zip1.writeZip(pth.join(destination, "06-jp_name.zip")); + + const zip2 = new Zip(pth.join(destination, "06-jp_name.zip")); + const text1 = zip2.readAsText(file); + assert(text1 === content, text1); + const entry2 = zip2.getEntry("test/" + file); + const text2 = zip2.readAsText(entry2); + assert(text2 === content, text2); + assert(entry2.header.flags_efs); + done(); }); - it("add files with jp filename (EUC-JP) into new zip", (done) => { - let zip1 = new Zip(); - zip1.addFile(iconv.encode('にほんご.txt','EUC-JP'), iconv.encode('にほんご','EUC-JP')); - zip1.addFile(iconv.encode('test/にほんご.txt','EUC-JP'), iconv.encode('にほんご','EUC-JP')); - zip1.writeZip(path.join(destination, "07-jp_name.zip")); - let zip2 = new Zip(path.join(destination, "07-jp_name.zip")); - let entry = zip2.getEntry(iconv.encode('にほんご.txt','EUC-JP')); - let text = zip2.readFile(entry); - //console.log(entry.toJSON()) - assert(text.equals(iconv.encode('にほんご','EUC-JP'))); - done() + it("add files with japanese filename (EUC-JP) into new zip", (done) => { + const encoding = "EUC-JP"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + const file = "にほんご.txt"; + const content = iconv.encode("にほんご", encoding); // buffer + + const zip1 = new Zip({ decoder }); + zip1.addFile(file, content); + zip1.addFile("test/" + file, content); + zip1.writeZip(pth.join(destination, "07-jp_name.zip")); + + const zip2 = new Zip(pth.join(destination, "07-jp_name.zip"), { decoder }); + let entry1 = zip2.getEntry(file); + let bufdata1 = zip2.readFile(entry1); + assert(bufdata1.equals(content)); + let entry2 = zip2.getEntry("test/" + file); + let bufdata2 = zip2.readFile(entry2); + assert(bufdata2.equals(content)); + assert(entry1.header.flags_efs === false); + assert(entry2.header.flags_efs === false); + done(); }); - it("add files with jp filename (Shift_JIS) into new zip", (done) => { - let zip1 = new Zip(); - zip1.addFile(iconv.encode('にほんご.txt','Shift_JIS'), iconv.encode('にほんご','Shift_JIS')); - zip1.addFile(iconv.encode('test/にほんご.txt','Shift_JIS'), iconv.encode('にほんご','Shift_JIS')); - zip1.writeZip(path.join(destination, "08-jp_name.zip")); - let zip2 = new Zip(path.join(destination, "08-jp_name.zip")); - let entry = zip2.getEntry(iconv.encode('にほんご.txt','Shift_JIS')); - let text = zip2.readFile(entry); - //console.log(entry.toJSON()) - assert(text.equals(iconv.encode('にほんご','Shift_JIS'))); - done() + it("add files with japanese filename (Shift_JIS) into new zip", (done) => { + const encoding = "Shift_JIS"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + const file = "にほんご.txt"; + const content = "にほんご"; + const bufdata = iconv.encode(content, "utf16le"); // buffer + + const zip1 = new Zip({ decoder }); + zip1.addFile(file, bufdata); + zip1.addFile("test/" + file, bufdata); + zip1.writeZip(pth.join(destination, "08-jp_name.zip")); + + const zip2 = new Zip(pth.join(destination, "08-jp_name.zip"), { decoder }); + let text1 = zip2.readAsText(file, "utf16le"); + assert(text1 === content, text1); + let text2 = zip2.readAsText("test/" + file, "utf16le"); + assert(text2 === content, text2); + done(); + }); + + // hebrew (writing left to right) + it("add files with hebrew filename (UTF-8) into new zip", (done) => { + const file = "שפה עברית.txt"; + const content = "יונה לבנה קטנה עפה מעל אנגליה"; + + const zip1 = new Zip(); + zip1.addFile(file, content); + zip1.addFile("test/" + file, content); + zip1.writeZip(pth.join(destination, "09-heb_name.zip")); + + const zip2 = new Zip(pth.join(destination, "09-heb_name.zip")); + const text1 = zip2.readAsText(file); + assert(text1 === content, text1); + const entry2 = zip2.getEntry("test/" + file); + const text2 = zip2.readAsText(entry2); + assert(text2 === content, text2); + assert(entry2.header.flags_efs); + done(); + }); + + it("add files with hebrew filename (win1255) into new zip", (done) => { + const encoding = "win1255"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + const file = "שפה עברית.txt"; + const content = "יונה לבנה קטנה עפה מעל אנגליה"; + const bufdata = iconv.encode(content, "utf16le"); // buffer + + const zip1 = new Zip({ decoder }); + zip1.addFile(file, bufdata); + zip1.addFile("test/" + file, bufdata); + zip1.writeZip(pth.join(destination, "10-heb_name.zip")); + + const zip2 = new Zip(pth.join(destination, "10-heb_name.zip"), { decoder }); + let text1 = zip2.readAsText(file, "utf16le"); + assert(text1 === content, text1); + let text2 = zip2.readAsText("test/" + file, "utf16le"); + assert(text2 === content, text2); + done(); + }); + + // Cyrillic + it("add files with bulgarian filename (win1251) into new zip", (done) => { + const encoding = "win1251"; + const decoder = { + encode: (data) => iconv.encode(data, encoding), + decode: (data) => iconv.decode(data, encoding) + }; + + const file = "Български.txt"; + const content = "Приключенията на таралежа"; + const bufdata = iconv.encode(content, "utf16le"); // buffer + + const zip1 = new Zip({ decoder }); + zip1.addFile(file, bufdata); + zip1.addFile("test/" + file, bufdata); + zip1.writeZip(pth.join(destination, "11-bul_name.zip")); + + const zip2 = new Zip(pth.join(destination, "11-bul_name.zip"), { decoder }); + let entry1 = zip2.getEntry(file); + let text1 = zip2.readAsText(entry1, "utf16le"); + assert(text1 === content, text1); + let entry2 = zip2.getEntry("test/" + file); + let text2 = zip2.readAsText(entry2, "utf16le"); + assert(text2 === content, text2); + assert(entry1.header.flags_efs === false); + assert(entry2.header.flags_efs === false); + done(); }); + // Unicode symbols + it("add files with Unicode symbols filename (utf8) into new zip", (done) => { + const file = "Symbols⌛🙈🙉.txt"; + const content = "♜♞♝♛♚♝♞♜\n♟♟♟♟♟♟♟♟\n♙♙♙♙♙♙♙♙\n♖♘♗♕♔♗♘♖"; + const bufdata = iconv.encode(content, "utf16le"); // buffer + + const zip1 = new Zip(); + zip1.addFile(file, bufdata); + zip1.addFile("test/" + file, bufdata); + zip1.writeZip(pth.join(destination, "12-sym_name.zip")); + + const zip2 = new Zip(pth.join(destination, "12-sym_name.zip")); + let entry1 = zip2.getEntry(file); + let text1 = zip2.readAsText(entry1, "utf16le"); + assert(text1 === content, text1); + let entry2 = zip2.getEntry("test/" + file); + let text2 = zip2.readAsText(entry2, "utf16le"); + assert(text2 === content, text2); + assert(entry1.header.flags_efs); + assert(entry2.header.flags_efs); + done(); + }); }); diff --git a/zipEntry.js b/zipEntry.js index 9dc6b46..8c3053b 100644 --- a/zipEntry.js +++ b/zipEntry.js @@ -208,12 +208,6 @@ module.exports = function (/*Buffer*/ input) { _isDirectory = lastChar === 47 || lastChar === 92; _entryHeader.fileNameLength = _entryName.length; }, - set rawEntryName(val) { - _entryName = val; - var lastChar = _entryName[_entryName.length - 1]; - _isDirectory = lastChar === 47 || lastChar === 92; - _entryHeader.fileNameLength = _entryName.length; - }, get extra() { return _extra; diff --git a/zipFile.js b/zipFile.js index c8b657e..9cf0729 100644 --- a/zipFile.js +++ b/zipFile.js @@ -49,7 +49,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { entry = new ZipEntry(inBuffer); entry.header = inBuffer.slice(tmp, (tmp += Utils.Constants.CENHDR)); - entry.rawEntryName = inBuffer.slice(tmp, (tmp += entry.header.fileNameLength)); + entry.entryName = inBuffer.slice(tmp, (tmp += entry.header.fileNameLength)); if (entry.header.extraLength) { entry.extra = inBuffer.slice(tmp, (tmp += entry.header.extraLength)); @@ -60,7 +60,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { index += entry.header.entryHeaderSize; entryList[i] = entry; - entryTable[Utils.toBuffer(entry.rawEntryName)] = entry; + entryTable[entry.entryName] = entry; } } @@ -164,8 +164,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { if (!loadedEntries) { readEntries(); } - let rawEntryName = Buffer.isBuffer(entryName) ? entryName : Utils.toBuffer(entryName); - return entryTable[rawEntryName] || null; + return entryTable[entryName] || null; }, /** @@ -178,8 +177,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { readEntries(); } entryList.push(entry); - let rawEntryName = Buffer.isBuffer(entry) ? entry : Utils.toBuffer(entry.entryName); - entryTable[rawEntryName] = entry; + entryTable[entry.entryName] = entry; mainHeader.totalEntries = entryList.length; }, @@ -193,18 +191,17 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { if (!loadedEntries) { readEntries(); } - let rawEntryName = Buffer.isBuffer(entryName) ? entryName : Utils.toBuffer(entryName); - var entry = entryTable[rawEntryName]; + var entry = entryTable[entryName]; if (entry && entry.isDirectory) { var _self = this; this.getEntryChildren(entry).forEach(function (child) { - if (child.rawEntryName !== entryName) { - _self.deleteEntry(child.rawEntryName); + if (child.entryName !== entryName) { + _self.deleteEntry(child.entryName); } }); } entryList.splice(entryList.indexOf(entry), 1); - delete entryTable[rawEntryName]; + delete entryTable[entryName]; mainHeader.totalEntries = entryList.length; }, @@ -220,10 +217,11 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { } if (entry && entry.isDirectory) { const list = []; + const name = entry.entryName; + const len = name.length; entryList.forEach(function (zipEntry) { - // NOTE: this does not consider the overlap of different codes - if (zipEntry.entryName === entry.entryName) { + if (zipEntry.entryName.substr(0, len) === name) { list.push(zipEntry); } });