229 lines
6.7 KiB
JavaScript
229 lines
6.7 KiB
JavaScript
'use strict';
|
|
var binary = require('binary');
|
|
|
|
var convertDateTime = function (dosDate, dosTime) {
|
|
var year = ((dosDate >> 9) & 0x7F) + 1980;
|
|
var month = (dosDate >> 5) & 0x0F;
|
|
var day = dosDate & 0x1F;
|
|
|
|
var hour = (dosTime >> 11);
|
|
var minute = (dosTime >> 5) & 0x3F;
|
|
var second = (dosTime & 0x1F) * 2;
|
|
|
|
var result = new Date(year, month - 1, day, hour, minute, second, 0);
|
|
|
|
return result;
|
|
};
|
|
|
|
var convertGeneralPurposeFlags = function (value) {
|
|
var bits = [];
|
|
|
|
for (var i = 0; i < 16; i++) {
|
|
bits[i] = (value >> i) & 1;
|
|
}
|
|
|
|
return {
|
|
encrypted: !!bits[0],
|
|
compressionFlag1: !!bits[1],
|
|
compressionFlag2: !!bits[2],
|
|
useDataDescriptor: !!bits[3],
|
|
enhancedDeflating: !!bits[4],
|
|
compressedPatched: !!bits[5],
|
|
strongEncryption: !!bits[6],
|
|
utf8: !!bits[11],
|
|
encryptedCD: !!bits[13]
|
|
};
|
|
};
|
|
|
|
var parseExternalFileAttributes = function (externalAttributes, platform) {
|
|
var types = {
|
|
// In theory, any of these could be set. Realistically, though, it will
|
|
// be regular, directory or symlink
|
|
1: 'NamedPipe',
|
|
2: 'Character',
|
|
4: 'Directory',
|
|
6: 'Block',
|
|
8: 'File',
|
|
10: 'SymbolicLink',
|
|
12: 'Socket'
|
|
};
|
|
|
|
switch (platform) {
|
|
|
|
case 3: // Unix
|
|
return {
|
|
platform: 'Unix',
|
|
type: types[(externalAttributes >> 28) & 0x0F],
|
|
mode: (externalAttributes >> 16) & 0xFFF
|
|
};
|
|
|
|
// case 0: // MSDOS
|
|
default:
|
|
if (platform !== 0) {
|
|
console.warn('Possibly unsupported ZIP platform type, ' + platform);
|
|
}
|
|
|
|
var attribs = {
|
|
A: (externalAttributes >> 5) & 0x01,
|
|
D: (externalAttributes >> 4) & 0x01,
|
|
V: (externalAttributes >> 3) & 0x01,
|
|
S: (externalAttributes >> 2) & 0x01,
|
|
H: (externalAttributes >> 1) & 0x01,
|
|
R: externalAttributes & 0x01
|
|
};
|
|
|
|
// With no better guidance we'll make the default permissions ugo+r
|
|
var mode = parseInt('0444', 8);
|
|
|
|
if (attribs.D) {
|
|
mode |= parseInt('0111', 8); // Set the execute bit
|
|
}
|
|
|
|
if (!attribs.R) {
|
|
mode |= parseInt('0222', 8); // Set the write bit
|
|
}
|
|
|
|
mode &= ~process.umask();
|
|
|
|
return {
|
|
platform: 'DOS',
|
|
type: attribs.D ? 'Directory' : 'File',
|
|
mode: mode
|
|
};
|
|
}
|
|
};
|
|
|
|
var readEndRecord = function (buffer) {
|
|
var data = binary.parse(buffer)
|
|
.word32lu('signature')
|
|
.word16lu('diskNumber')
|
|
.word16lu('directoryStartDisk')
|
|
.word16lu('directoryEntryCountDisk')
|
|
.word16lu('directoryEntryCount')
|
|
.word32lu('directorySize')
|
|
.word32lu('directoryOffset')
|
|
.word16lu('commentLength')
|
|
.buffer('comment', 'commentLength')
|
|
.vars;
|
|
|
|
data.comment = data.comment.toString();
|
|
|
|
return data;
|
|
};
|
|
|
|
var directorySort = function (a, b) {
|
|
return a.relativeOffsetOfLocalHeader - b.relativeOffsetOfLocalHeader;
|
|
};
|
|
|
|
var readDirectory = function (buffer) {
|
|
var directory = [];
|
|
var current;
|
|
var index = 0;
|
|
|
|
while (index < buffer.length) {
|
|
current = binary.parse(buffer.slice(index, index + 46))
|
|
.word32lu('signature')
|
|
.word8lu('creatorSpecVersion')
|
|
.word8lu('creatorPlatform')
|
|
.word8lu('requiredSpecVersion')
|
|
.word8lu('requiredPlatform')
|
|
.word16lu('generalPurposeBitFlag')
|
|
.word16lu('compressionMethod')
|
|
.word16lu('lastModFileTime')
|
|
.word16lu('lastModFileDate')
|
|
.word32lu('crc32')
|
|
.word32lu('compressedSize')
|
|
.word32lu('uncompressedSize')
|
|
.word16lu('fileNameLength')
|
|
.word16lu('extraFieldLength')
|
|
.word16lu('fileCommentLength')
|
|
.word16lu('diskNumberStart')
|
|
.word16lu('internalFileAttributes')
|
|
.word32lu('externalFileAttributes')
|
|
.word32lu('relativeOffsetOfLocalHeader')
|
|
.vars;
|
|
|
|
index += 46;
|
|
|
|
current.generalPurposeFlags = convertGeneralPurposeFlags(current.generalPurposeBitFlag);
|
|
current.fileAttributes = parseExternalFileAttributes(current.externalFileAttributes, current.creatorPlatform);
|
|
|
|
current.modifiedTime = convertDateTime(current.lastModFileDate, current.lastModFileTime);
|
|
current.fileName = current.extraField = current.fileComment = '';
|
|
current.headerLength = 46 + current.fileNameLength + current.extraFieldLength + current.fileCommentLength;
|
|
|
|
if (current.fileNameLength > 0) {
|
|
current.fileName = buffer.slice(index, index + current.fileNameLength).toString();
|
|
index += current.fileNameLength;
|
|
}
|
|
|
|
if (current.extraFieldLength > 0) {
|
|
current.extraField = buffer.slice(index, index + current.extraFieldLength).toString();
|
|
index += current.extraFieldLength;
|
|
}
|
|
|
|
if (current.fileCommentLength > 0) {
|
|
current.fileComment = buffer.slice(index, index + current.fileCommentLength).toString();
|
|
index += current.fileCommentLength;
|
|
}
|
|
|
|
if (current.fileAttributes.type !== 'Directory' && current.fileName.substr(-1) === '/') {
|
|
// TODO: check that this is a reasonable check
|
|
current.fileAttributes.type = 'Directory';
|
|
}
|
|
|
|
directory.push(current);
|
|
}
|
|
|
|
directory.sort(directorySort);
|
|
|
|
return directory;
|
|
};
|
|
|
|
var readFileEntry = function (buffer) {
|
|
var index = 0;
|
|
|
|
var fileEntry = binary.parse(buffer.slice(index, 30))
|
|
.word32lu('signature')
|
|
.word16lu('versionNeededToExtract')
|
|
.word16lu('generalPurposeBitFlag')
|
|
.word16lu('compressionMethod')
|
|
.word16lu('lastModFileTime')
|
|
.word16lu('lastModFileDate')
|
|
.word32lu('crc32')
|
|
.word32lu('compressedSize')
|
|
.word32lu('uncompressedSize')
|
|
.word16lu('fileNameLength')
|
|
.word16lu('extraFieldLength')
|
|
.vars;
|
|
|
|
index += 30;
|
|
|
|
fileEntry.fileName = fileEntry.extraField = '';
|
|
|
|
fileEntry.entryLength = 30 + fileEntry.fileNameLength + fileEntry.extraFieldLength;
|
|
|
|
if (fileEntry.entryLength > structures.maxFileEntrySize) {
|
|
throw new Error('File entry unexpectedly large: ' + fileEntry.entryLength + ' (max: ' + structures.maxFileEntrySize + ')');
|
|
}
|
|
|
|
if (fileEntry.fileNameLength > 0) {
|
|
fileEntry.fileName = buffer.slice(index, index + fileEntry.fileNameLength).toString();
|
|
index += fileEntry.fileNameLength;
|
|
}
|
|
|
|
if (fileEntry.extraFieldLength > 0) {
|
|
fileEntry.extraField = buffer.slice(index, index + fileEntry.extraFieldLength).toString();
|
|
index += fileEntry.extraFieldLength;
|
|
}
|
|
|
|
return fileEntry;
|
|
};
|
|
|
|
var structures = module.exports = {
|
|
readEndRecord: readEndRecord,
|
|
readDirectory: readDirectory,
|
|
readFileEntry: readFileEntry,
|
|
maxFileEntrySize: 4096
|
|
};
|