'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 };