Skip to content

Commit a7e8932

Browse files
authoredNov 19, 2020
Merge pull request #331 from 5saviahv/master
Modified addLocalFile so it will read file stats from file
2 parents 9d2eb0b + 7db0eda commit a7e8932

File tree

7 files changed

+206
-82
lines changed

7 files changed

+206
-82
lines changed
 

‎adm-zip.js

+95-74
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,26 @@ module.exports = function (/**String*/input) {
5656
return null;
5757
}
5858

59+
function fixPath(zipPath){
60+
// convert windows file separators
61+
zipPath = zipPath.split("\\").join("/");
62+
// add separator if it wasnt given
63+
if (zipPath.charAt(zipPath.length - 1) !== "/") {
64+
zipPath += "/";
65+
}
66+
return zipPath;
67+
}
68+
5969
return {
6070
/**
6171
* Extracts the given entry from the archive and returns the content as a Buffer object
6272
* @param entry ZipEntry object or String with the full path of the entry
6373
*
6474
* @return Buffer or Null in case of error
6575
*/
66-
readFile: function (/**Object*/entry) {
76+
readFile: function (/**Object*/entry, /*String, Buffer*/pass) {
6777
var item = getEntry(entry);
68-
return item && item.getData() || null;
78+
return item && item.getData(pass) || null;
6979
},
7080

7181
/**
@@ -208,23 +218,22 @@ module.exports = function (/**String*/input) {
208218
* @param zipPath Optional path inside the zip
209219
* @param zipName Optional name for the file
210220
*/
211-
addLocalFile: function (/**String*/localPath, /**String=*/zipPath, /**String=*/zipName) {
221+
addLocalFile: function (/**String*/localPath, /**String=*/zipPath, /**String=*/zipName, /**String*/comment) {
212222
if (fs.existsSync(localPath)) {
213-
if (zipPath) {
214-
zipPath = zipPath.split("\\").join("/");
215-
if (zipPath.charAt(zipPath.length - 1) !== "/") {
216-
zipPath += "/";
217-
}
218-
} else {
219-
zipPath = "";
220-
}
223+
// fix ZipPath
224+
zipPath = (zipPath) ? fixPath(zipPath) : "";
225+
226+
// p - local file name
221227
var p = localPath.split("\\").join("/").split("/").pop();
222228

223-
if (zipName) {
224-
this.addFile(zipPath + zipName, fs.readFileSync(localPath), "", 0)
225-
} else {
226-
this.addFile(zipPath + p, fs.readFileSync(localPath), "", 0)
227-
}
229+
// add file name into zippath
230+
zipPath += (zipName) ? zipName : p;
231+
232+
// read file attributes
233+
const _attr = fs.statSync(localPath);
234+
235+
// add file into zip file
236+
this.addFile(zipPath, fs.readFileSync(localPath), comment, _attr)
228237
} else {
229238
throw new Error(Utils.Errors.FILE_NOT_FOUND.replace("%s", localPath));
230239
}
@@ -238,54 +247,47 @@ module.exports = function (/**String*/input) {
238247
* @param filter optional RegExp or Function if files match will
239248
* be included.
240249
*/
241-
addLocalFolder: function (/**String*/localPath, /**String=*/zipPath, /**=RegExp|Function*/filter) {
242-
if (filter === undefined) {
243-
filter = function () {
244-
return true;
245-
};
246-
} else if (filter instanceof RegExp) {
247-
filter = function (filter) {
248-
return function (filename) {
249-
return filter.test(filename);
250-
}
251-
}(filter);
252-
}
253-
254-
if (zipPath) {
255-
zipPath = zipPath.split("\\").join("/");
256-
if (zipPath.charAt(zipPath.length - 1) !== "/") {
257-
zipPath += "/";
258-
}
259-
} else {
260-
zipPath = "";
261-
}
262-
// normalize the path first
263-
localPath = pth.normalize(localPath);
264-
localPath = localPath.split("\\").join("/"); //windows fix
265-
if (localPath.charAt(localPath.length - 1) !== "/")
266-
localPath += "/";
267-
268-
if (fs.existsSync(localPath)) {
269-
270-
var items = Utils.findFiles(localPath),
271-
self = this;
272-
273-
if (items.length) {
274-
items.forEach(function (path) {
275-
var p = path.split("\\").join("/").replace(new RegExp(localPath.replace(/(\(|\)|\$)/g, '\\$1'), 'i'), ""); //windows fix
276-
if (filter(p)) {
277-
if (p.charAt(p.length - 1) !== "/") {
278-
self.addFile(zipPath + p, fs.readFileSync(path), "", 0)
279-
} else {
280-
self.addFile(zipPath + p, Buffer.alloc(0), "", 0)
281-
}
282-
}
283-
});
284-
}
285-
} else {
286-
throw new Error(Utils.Errors.FILE_NOT_FOUND.replace("%s", localPath));
287-
}
288-
},
250+
addLocalFolder: function (/**String*/localPath, /**String=*/zipPath, /**=RegExp|Function*/filter) {
251+
// Prepare filter
252+
if (filter instanceof RegExp) { // if filter is RegExp wrap it
253+
filter = (function (rx){
254+
return function (filename) {
255+
return rx.test(filename);
256+
}
257+
})(filter);
258+
} else if ('function' !== typeof filter) { // if filter is not function we will replace it
259+
filter = function () {
260+
return true;
261+
};
262+
}
263+
264+
// fix ZipPath
265+
zipPath = (zipPath) ? fixPath(zipPath) : "";
266+
267+
// normalize the path first
268+
localPath = pth.normalize(localPath);
269+
270+
if (fs.existsSync(localPath)) {
271+
272+
var items = Utils.findFiles(localPath),
273+
self = this;
274+
275+
if (items.length) {
276+
items.forEach(function (filepath) {
277+
var p = pth.relative(localPath, filepath).split("\\").join("/"); //windows fix
278+
if (filter(p)) {
279+
if (filepath.charAt(filepath.length - 1) !== pth.sep) {
280+
self.addFile(zipPath + p, fs.readFileSync(filepath), "", fs.statSync(filepath));
281+
} else {
282+
self.addFile(zipPath + p + '/', Buffer.alloc(0), "", 0);
283+
}
284+
}
285+
});
286+
}
287+
} else {
288+
throw new Error(Utils.Errors.FILE_NOT_FOUND.replace("%s", localPath));
289+
}
290+
},
289291

290292
/**
291293
* Asynchronous addLocalFile
@@ -376,19 +378,38 @@ module.exports = function (/**String*/input) {
376378
* @param attr
377379
*/
378380
addFile: function (/**String*/entryName, /**Buffer*/content, /**String*/comment, /**Number*/attr) {
381+
// prepare new entry
379382
var entry = new ZipEntry();
380383
entry.entryName = entryName;
381384
entry.comment = comment || "";
382385

383-
if (!attr) {
384-
if (entry.isDirectory) {
385-
attr = (0o40755 << 16) | 0x10; // (permissions drwxr-xr-x) + (MS-DOS directory flag)
386-
} else {
387-
attr = 0o644 << 16; // permissions -r-wr--r--
386+
var isStat = ('object' === typeof attr) && (attr instanceof fs.Stats);
387+
388+
// last modification time from file stats
389+
if (isStat){
390+
entry.header.time = attr.mtime;
391+
}
392+
393+
// Set file attribute
394+
var fileattr = (entry.isDirectory) ? 0x10 : 0; // (MS-DOS directory flag)
395+
396+
// extended attributes field for Unix
397+
if('win32' !== process.platform){
398+
// set file type either S_IFDIR / S_IFREG
399+
var unix = (entry.isDirectory) ? 0x4000 : 0x8000;
400+
401+
if (isStat) { // File attributes from file stats
402+
unix |= (0xfff & attr.mode)
403+
}else if ('number' === typeof attr){ // attr from given attr values
404+
unix |= (0xfff & attr);
405+
}else{ // Default values:
406+
unix |= (entry.isDirectory) ? 0o755 : 0o644; // permissions (drwxr-xr-x) or (-r-wr--r--)
388407
}
408+
409+
fileattr = (fileattr | (unix << 16)) >>> 0; // add attributes
389410
}
390411

391-
entry.attr = attr;
412+
entry.attr = fileattr;
392413

393414
entry.setData(content);
394415
_zip.setEntry(entry);
@@ -482,7 +503,7 @@ module.exports = function (/**String*/input) {
482503
* Test the archive
483504
*
484505
*/
485-
test: function () {
506+
test: function (pass) {
486507
if (!_zip) {
487508
return false;
488509
}
@@ -492,7 +513,7 @@ module.exports = function (/**String*/input) {
492513
if (entry.isDirectory) {
493514
continue;
494515
}
495-
var content = _zip.entries[entry].getData();
516+
var content = _zip.entries[entry].getData(pass);
496517
if (!content) {
497518
return false;
498519
}
@@ -510,7 +531,7 @@ module.exports = function (/**String*/input) {
510531
* @param overwrite If the file already exists at the target path, the file will be overwriten if this is true.
511532
* Default is FALSE
512533
*/
513-
extractAllTo: function (/**String*/targetPath, /**Boolean*/overwrite) {
534+
extractAllTo: function (/**String*/targetPath, /**Boolean*/overwrite, /*String, Buffer*/pass) {
514535
overwrite = overwrite || false;
515536
if (!_zip) {
516537
throw new Error(Utils.Errors.NO_ZIP);
@@ -521,7 +542,7 @@ module.exports = function (/**String*/input) {
521542
Utils.makeDir(entryName);
522543
return;
523544
}
524-
var content = entry.getData();
545+
var content = entry.getData(pass);
525546
if (!content) {
526547
throw new Error(Utils.Errors.CANT_EXTRACT_FILE);
527548
}

‎headers/entryHeader.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ var Utils = require("../util"),
33

44
/* The central directory file header */
55
module.exports = function () {
6-
var _verMade = 0x0A,
6+
var _verMade = 0x14,
77
_version = 0x0A,
88
_flags = 0,
99
_method = 0,
@@ -20,6 +20,15 @@ module.exports = function () {
2020
_attr = 0,
2121
_offset = 0;
2222

23+
switch(process.platform){
24+
case 'win32':
25+
_verMade |= 0x0A00;
26+
case 'darwin':
27+
_verMade |= 0x1300;
28+
default:
29+
_verMade |= 0x0300;
30+
}
31+
2332
var _dataHeader = {};
2433

2534
function setTime(val) {
@@ -47,7 +56,16 @@ module.exports = function () {
4756
set flags (val) { _flags = val; },
4857

4958
get method () { return _method; },
50-
set method (val) { _method = val; },
59+
set method (val) {
60+
switch (val){
61+
case Constants.STORED:
62+
this.version = 10;
63+
case Constants.DEFLATED:
64+
default:
65+
this.version = 20;
66+
}
67+
_method = val;
68+
},
5169

5270
get time () { return new Date(
5371
((_time >> 25) & 0x7f) + 1980,

‎methods/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
exports.Deflater = require("./deflater");
2-
exports.Inflater = require("./inflater");
2+
exports.Inflater = require("./inflater");
3+
exports.ZipCrypto = require("./zipcrypto");

‎methods/zipcrypto.js

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// generate CRC32 lookup table
2+
const crctable = (new Uint32Array(256)).map((t,crc)=>{
3+
for(let j=0;j<8;j++){
4+
if (0 !== (crc & 1)){
5+
crc = (crc >>> 1) ^ 0xEDB88320
6+
}else{
7+
crc >>>= 1
8+
}
9+
}
10+
return crc>>>0;
11+
});
12+
13+
function make_decrypter(/*Buffer*/pwd){
14+
// C-style uInt32 Multiply
15+
const uMul = (a,b) => Math.imul(a, b) >>> 0;
16+
// Initialize keys with default values
17+
const keys = new Uint32Array([0x12345678, 0x23456789, 0x34567890]);
18+
// crc32 byte update
19+
const crc32update = (pCrc32, bval) => {
20+
return crctable[(pCrc32 ^ bval) & 0xff] ^ (pCrc32 >>> 8);
21+
}
22+
// update keys with byteValues
23+
const updateKeys = (byteValue) => {
24+
keys[0] = crc32update(keys[0], byteValue);
25+
keys[1] += keys[0] & 0xff;
26+
keys[1] = uMul(keys[1], 134775813) + 1;
27+
keys[2] = crc32update(keys[2], keys[1] >>> 24);
28+
}
29+
30+
// 1. Stage initialize key
31+
const pass = (Buffer.isBuffer(pwd)) ? pwd : Buffer.from(pwd);
32+
for(let i=0; i< pass.length; i++){
33+
updateKeys(pass[i]);
34+
}
35+
36+
// return decrypter function
37+
return function (/*Buffer*/data){
38+
if (!Buffer.isBuffer(data)){
39+
throw 'decrypter needs Buffer'
40+
}
41+
// result - we create new Buffer for results
42+
const result = Buffer.alloc(data.length);
43+
let pos = 0;
44+
// process input data
45+
for(let c of data){
46+
const k = (keys[2] | 2) >>> 0; // key
47+
c ^= (uMul(k, k^1) >> 8) & 0xff; // decode
48+
result[pos++] = c; // Save Value
49+
updateKeys(c); // update keys with decoded byte
50+
}
51+
return result;
52+
}
53+
}
54+
55+
function decrypt(/*Buffer*/ data, /*Object*/header, /*String, Buffer*/ pwd){
56+
if (!data || !Buffer.isBuffer(data) || data.length < 12) {
57+
return Buffer.alloc(0);
58+
}
59+
60+
// We Initialize and generate decrypting function
61+
const decrypter = make_decrypter(pwd);
62+
63+
// check - for testing password
64+
const check = header.crc >>> 24;
65+
// decrypt salt what is always 12 bytes and is a part of file content
66+
const testbyte = decrypter(data.slice(0, 12))[11];
67+
68+
// does password meet expectations
69+
if (check !== testbyte){
70+
throw 'ADM-ZIP: Wrong Password';
71+
}
72+
73+
// decode content
74+
return decrypter(data.slice(12));
75+
}
76+
77+
module.exports = {decrypt};

‎package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@
2525
"util",
2626
"zipEntry.js",
2727
"zipFile.js",
28-
"MIT-LICENSE.txt"
28+
"LICENSE"
2929
],
3030
"main": "adm-zip.js",
3131
"repository": {
3232
"type": "git",
3333
"url": "https://github.com/cthackers/adm-zip.git"
3434
},
3535
"engines": {
36-
"node": ">=0.3.0"
36+
"node": ">=6.0"
3737
},
3838
"devDependencies": {
3939
"chai": "^4.1.2",

‎util/fattr.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ module.exports = function(/*String*/path) {
2626
_obj.directory = _stat.isDirectory();
2727
_obj.mtime = _stat.mtime;
2828
_obj.atime = _stat.atime;
29-
_obj.executable = !!(1 & parseInt ((_stat.mode & parseInt ("777", 8)).toString (8)[0]));
30-
_obj.readonly = !!(2 & parseInt ((_stat.mode & parseInt ("777", 8)).toString (8)[0]));
29+
_obj.executable = (0o111 & _stat.mode) != 0; // file is executable who ever har right not just owner
30+
_obj.readonly = (0o200 & _stat.mode) == 0; // readonly if owner has no write right
3131
_obj.hidden = pth.basename(_path)[0] === ".";
3232
} else {
3333
console.warn("Invalid path: " + _path)

‎zipEntry.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ module.exports = function (/*Buffer*/input) {
3434
return true;
3535
}
3636

37-
function decompress(/*Boolean*/async, /*Function*/callback, /*String*/pass) {
37+
function decompress(/*Boolean*/async, /*Function*/callback, /*String, Buffer*/pass) {
3838
if(typeof callback === 'undefined' && typeof async === 'string') {
3939
pass=async;
4040
async=void 0;
@@ -54,6 +54,13 @@ module.exports = function (/*Buffer*/input) {
5454
return compressedData;
5555
}
5656

57+
if (_entryHeader.encripted){
58+
if ('string' !== typeof pass && !Buffer.isBuffer(pass)){
59+
throw new Error('ADM-ZIP: Incompatible password parameter');
60+
}
61+
compressedData = Methods.ZipCrypto.decrypt(compressedData, _entryHeader, pass);
62+
}
63+
5764
var data = Buffer.alloc(_entryHeader.size);
5865

5966
switch (_entryHeader.method) {

0 commit comments

Comments
 (0)
Please sign in to comment.