var ZipEntry = require("./zipEntry"), | |
Headers = require("./headers"), | |
Utils = require("./util"); | |
module.exports = function(/*String|Buffer*/input, /*Number*/inputType) { | |
var entryList = [], | |
entryTable = {}, | |
_comment = new Buffer(0), | |
filename = "", | |
fs = require("fs"), | |
inBuffer = null, | |
mainHeader = new Headers.MainHeader(); | |
if (inputType == Utils.Constants.FILE) { | |
// is a filename | |
filename = input; | |
inBuffer = fs.readFileSync(filename); | |
readMainHeader(); | |
} else if (inputType == Utils.Constants.BUFFER) { | |
// is a memory buffer | |
inBuffer = input; | |
readMainHeader(); | |
} else { | |
// none. is a new file | |
} | |
function readEntries() { | |
entryTable = {}; | |
entryList = new Array(mainHeader.diskEntries); // total number of entries | |
var index = mainHeader.offset; // offset of first CEN header | |
for(var i = 0; i < entryList.length; i++) { | |
var tmp = index, | |
entry = new ZipEntry(inBuffer); | |
entry.header = inBuffer.slice(tmp, tmp += Utils.Constants.CENHDR); | |
entry.entryName = inBuffer.slice(tmp, tmp += entry.header.fileNameLength); | |
if (entry.header.extraLength) { | |
entry.extra = inBuffer.slice(tmp, tmp += entry.header.extraLength); | |
} | |
if (entry.header.commentLength) | |
entry.comment = inBuffer.slice(tmp, tmp + entry.header.commentLength); | |
index += entry.header.entryHeaderSize; | |
entryList[i] = entry; | |
entryTable[entry.entryName] = entry; | |
} | |
} | |
function readMainHeader() { | |
var i = inBuffer.length - Utils.Constants.ENDHDR, // END header size | |
n = Math.max(0, i - 0xFFFF), // 0xFFFF is the max zip file comment length | |
endOffset = 0; // Start offset of the END header | |
for (i; i >= n; i--) { | |
if (inBuffer[i] != 0x50) continue; // quick check that the byte is 'P' | |
if (inBuffer.readUInt32LE(i) == Utils.Constants.ENDSIG) { // "PK\005\006" | |
endOffset = i; | |
break; | |
} | |
} | |
if (!endOffset) | |
throw Utils.Errors.INVALID_FORMAT; | |
mainHeader.loadFromBinary(inBuffer.slice(endOffset, endOffset + Utils.Constants.ENDHDR)); | |
if (mainHeader.commentLength) { | |
_comment = inBuffer.slice(endOffset + Utils.Constants.ENDHDR); | |
} | |
readEntries(); | |
} | |
return { | |
/** | |
* Returns an array of ZipEntry objects existent in the current opened archive | |
* @return Array | |
*/ | |
get entries () { | |
return entryList; | |
}, | |
/** | |
* Archive comment | |
* @return {String} | |
*/ | |
get comment () { return _comment.toString(); }, | |
set comment(val) { | |
mainHeader.commentLength = val.length; | |
_comment = val; | |
}, | |
/** | |
* Returns a reference to the entry with the given name or null if entry is inexistent | |
* | |
* @param entryName | |
* @return ZipEntry | |
*/ | |
getEntry : function(/*String*/entryName) { | |
return entryTable[entryName] || null; | |
}, | |
/** | |
* Adds the given entry to the entry list | |
* | |
* @param entry | |
*/ | |
setEntry : function(/*ZipEntry*/entry) { | |
entryList.push(entry); | |
entryTable[entry.entryName] = entry; | |
mainHeader.totalEntries = entryList.length; | |
}, | |
/** | |
* Removes the entry with the given name from the entry list. | |
* | |
* If the entry is a directory, then all nested files and directories will be removed | |
* @param entryName | |
*/ | |
deleteEntry : function(/*String*/entryName) { | |
var entry = entryTable[entryName]; | |
if (entry && entry.isDirectory) { | |
var _self = this; | |
this.getEntryChildren(entry).forEach(function(child) { | |
if (child.entryName != entryName) { | |
_self.deleteEntry(child.entryName) | |
} | |
}) | |
} | |
entryList.splice(entryList.indexOf(entry), 1); | |
delete(entryTable[entryName]); | |
mainHeader.totalEntries = entryList.length; | |
}, | |
/** | |
* Iterates and returns all nested files and directories of the given entry | |
* | |
* @param entry | |
* @return Array | |
*/ | |
getEntryChildren : function(/*ZipEntry*/entry) { | |
if (entry.isDirectory) { | |
var list = [], | |
name = entry.entryName, | |
len = name.length; | |
entryList.forEach(function(zipEntry) { | |
if (zipEntry.entryName.substr(0, len) == name) { | |
list.push(zipEntry); | |
} | |
}); | |
return list; | |
} | |
return [] | |
}, | |
/** | |
* Returns the zip file | |
* | |
* @return Buffer | |
*/ | |
compressToBuffer : function() { | |
if (entryList.length > 1) { | |
entryList.sort(function(a, b) { | |
var nameA = a.entryName.toLowerCase(); | |
var nameB = b.entryName.toLowerCase(); | |
if (nameA < nameB) {return -1} | |
if (nameA > nameB) {return 1} | |
return 0; | |
}); | |
} | |
var totalSize = 0, | |
dataBlock = [], | |
entryHeaders = [], | |
dindex = 0; | |
mainHeader.size = 0; | |
mainHeader.offset = 0; | |
entryList.forEach(function(entry) { | |
entry.header.offset = dindex; | |
// compress data and set local and entry header accordingly. Reason why is called first | |
var compressedData = entry.getCompressedData(); | |
// data header | |
var dataHeader = entry.header.dataHeaderToBinary(); | |
var postHeader = new Buffer(entry.entryName + entry.extra.toString()); | |
var dataLength = dataHeader.length + postHeader.length + compressedData.length; | |
dindex += dataLength; | |
dataBlock.push(dataHeader); | |
dataBlock.push(postHeader); | |
dataBlock.push(compressedData); | |
var entryHeader = entry.packHeader(); | |
entryHeaders.push(entryHeader); | |
mainHeader.size += entryHeader.length; | |
totalSize += (dataLength + entryHeader.length); | |
}); | |
totalSize += mainHeader.mainHeaderSize; // also includes zip file comment length | |
// point to end of data and begining of central directory first record | |
mainHeader.offset = dindex; | |
dindex = 0; | |
var outBuffer = new Buffer(totalSize); | |
dataBlock.forEach(function(content) { | |
content.copy(outBuffer, dindex); // write data blocks | |
dindex += content.length; | |
}); | |
entryHeaders.forEach(function(content) { | |
content.copy(outBuffer, dindex); // write central directory entries | |
dindex += content.length; | |
}); | |
var mh = mainHeader.toBinary(); | |
if (_comment) { | |
_comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment | |
} | |
mh.copy(outBuffer, dindex); // write main header | |
return outBuffer | |
}, | |
toAsyncBuffer : function(/*Function*/onSuccess,/*Function*/onFail,/*Function*/onItemStart,/*Function*/onItemEnd) { | |
if (entryList.length > 1) { | |
entryList.sort(function(a, b) { | |
var nameA = a.entryName.toLowerCase(); | |
var nameB = b.entryName.toLowerCase(); | |
if (nameA > nameB) {return -1} | |
if (nameA < nameB) {return 1} | |
return 0; | |
}); | |
} | |
var totalSize = 0, | |
dataBlock = [], | |
entryHeaders = [], | |
dindex = 0; | |
mainHeader.size = 0; | |
mainHeader.offset = 0; | |
var compress=function(entryList){ | |
var self=arguments.callee; | |
var entry; | |
if(entryList.length){ | |
var entry=entryList.pop(); | |
var name=entry.entryName + entry.extra.toString(); | |
if(onItemStart)onItemStart(name); | |
entry.getCompressedDataAsync(function(compressedData){ | |
if(onItemEnd)onItemEnd(name); | |
entry.header.offset = dindex; | |
// data header | |
var dataHeader = entry.header.dataHeaderToBinary(); | |
var postHeader = new Buffer(name); | |
var dataLength = dataHeader.length + postHeader.length + compressedData.length; | |
dindex += dataLength; | |
dataBlock.push(dataHeader); | |
dataBlock.push(postHeader); | |
dataBlock.push(compressedData); | |
var entryHeader = entry.packHeader(); | |
entryHeaders.push(entryHeader); | |
mainHeader.size += entryHeader.length; | |
totalSize += (dataLength + entryHeader.length); | |
if(entryList.length){ | |
self(entryList); | |
}else{ | |
totalSize += mainHeader.mainHeaderSize; // also includes zip file comment length | |
// point to end of data and begining of central directory first record | |
mainHeader.offset = dindex; | |
dindex = 0; | |
var outBuffer = new Buffer(totalSize); | |
dataBlock.forEach(function(content) { | |
content.copy(outBuffer, dindex); // write data blocks | |
dindex += content.length; | |
}); | |
entryHeaders.forEach(function(content) { | |
content.copy(outBuffer, dindex); // write central directory entries | |
dindex += content.length; | |
}); | |
var mh = mainHeader.toBinary(); | |
if (_comment) { | |
_comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment | |
} | |
mh.copy(outBuffer, dindex); // write main header | |
onSuccess(outBuffer); | |
} | |
}); | |
} | |
}; | |
compress(entryList); | |
} | |
} | |
}; |