"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/decompress-zip/lib/decompress-zip.js" (7 Feb 2017, 9567 Bytes) of archive /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Javascript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 'use strict';
    2 
    3 // The zip file spec is at http://www.pkware.com/documents/casestudies/APPNOTE.TXT
    4 // TODO: There is fair chunk of the spec that I have ignored. Need to add
    5 // assertions everywhere to make sure that we are not dealing with a ZIP type
    6 // that I haven't designed for. Things like spanning archives, non-DEFLATE
    7 // compression, encryption, etc.
    8 var fs = require('graceful-fs');
    9 var Q = require('q');
   10 var path = require('path');
   11 var util = require('util');
   12 var events = require('events');
   13 var structures = require('./structures');
   14 var signatures = require('./signatures');
   15 var extractors = require('./extractors');
   16 var FileDetails = require('./file-details');
   17 
   18 var fstat = Q.denodeify(fs.fstat);
   19 var read = Q.denodeify(fs.read);
   20 var fopen = Q.denodeify(fs.open);
   21 
   22 function DecompressZip(filename) {
   23     events.EventEmitter.call(this);
   24 
   25     this.filename = filename;
   26     this.stats = null;
   27     this.fd = null;
   28     this.chunkSize = 1024 * 1024; // Buffer up to 1Mb at a time
   29     this.dirCache = {};
   30 
   31     // When we need a resource, we should check if there is a promise for it
   32     // already and use that. If the promise is already fulfilled we don't do the
   33     // async work again and we get to queue up dependant tasks.
   34     this._p = {}; // _p instead of _promises because it is a lot easier to read
   35 }
   36 
   37 util.inherits(DecompressZip, events.EventEmitter);
   38 
   39 DecompressZip.prototype.openFile = function () {
   40     return fopen(this.filename, 'r');
   41 };
   42 
   43 DecompressZip.prototype.closeFile = function () {
   44     if (this.fd) {
   45         fs.closeSync(this.fd);
   46         this.fd = null;
   47     }
   48 };
   49 
   50 DecompressZip.prototype.statFile = function (fd) {
   51     this.fd = fd;
   52     return fstat(fd);
   53 };
   54 
   55 DecompressZip.prototype.list = function () {
   56     var self = this;
   57 
   58     this.getFiles()
   59     .then(function (files) {
   60         var result = [];
   61 
   62         files.forEach(function (file) {
   63             result.push(file.path);
   64         });
   65 
   66         self.emit('list', result);
   67     })
   68     .fail(function (error) {
   69         self.emit('error', error);
   70     })
   71     .fin(self.closeFile.bind(self));
   72 
   73     return this;
   74 };
   75 
   76 DecompressZip.prototype.extract = function (options) {
   77     var self = this;
   78 
   79     options = options || {};
   80     options.path = options.path || '.';
   81     options.filter = options.filter || null;
   82     options.follow = !!options.follow;
   83     options.strip = +options.strip || 0;
   84 
   85     this.getFiles()
   86     .then(function (files) {
   87         var copies = [];
   88 
   89         if (options.filter) {
   90             files = files.filter(options.filter);
   91         }
   92 
   93         if (options.follow) {
   94             copies = files.filter(function (file) {
   95                 return file.type === 'SymbolicLink';
   96             });
   97             files = files.filter(function (file) {
   98                 return file.type !== 'SymbolicLink';
   99             });
  100         }
  101 
  102         if (options.strip) {
  103             files = files.map(function (file) {
  104                 if (file.type !== 'Directory') {
  105                     // we don't use `path.sep` as we're using `/` in Windows too
  106                     var dir = file.parent.split('/');
  107                     var filename = file.filename;
  108 
  109                     if (options.strip > dir.length) {
  110                         throw new Error('You cannot strip more levels than there are directories');
  111                     } else {
  112                         dir = dir.slice(options.strip);
  113                     }
  114 
  115                     file.path = path.join(dir.join(path.sep), filename);
  116                     return file;
  117                 }
  118             });
  119         }
  120 
  121         return self.extractFiles(files, options)
  122         .then(self.extractFiles.bind(self, copies, options));
  123     })
  124     .then(function (results) {
  125         self.emit('extract', results);
  126     })
  127     .fail(function (error) {
  128         self.emit('error', error);
  129     })
  130     .fin(self.closeFile.bind(self));
  131 
  132     return this;
  133 };
  134 
  135 // Utility methods
  136 DecompressZip.prototype.getSearchBuffer = function (stats) {
  137     var size = Math.min(stats.size, this.chunkSize);
  138     this.stats = stats;
  139     return this.getBuffer(stats.size - size, stats.size);
  140 };
  141 
  142 DecompressZip.prototype.getBuffer = function (start, end) {
  143     var size = end - start;
  144     return read(this.fd, new Buffer(size), 0, size, start)
  145     .then(function (result) {
  146         return result[1];
  147     });
  148 };
  149 
  150 DecompressZip.prototype.findEndOfDirectory = function (buffer) {
  151     var index = buffer.length - 3;
  152     var chunk = '';
  153 
  154     // Apparently the ZIP spec is not very good and it is impossible to
  155     // guarantee that you have read a zip file correctly, or to determine
  156     // the location of the CD without hunting.
  157     // Search backwards through the buffer, as it is very likely to be near the
  158     // end of the file.
  159     while (index > Math.max(buffer.length - this.chunkSize, 0) && chunk !== signatures.END_OF_CENTRAL_DIRECTORY) {
  160         index--;
  161         chunk = buffer.readUInt32LE(index);
  162     }
  163 
  164     if (chunk !== signatures.END_OF_CENTRAL_DIRECTORY) {
  165         throw new Error('Could not find the End of Central Directory Record');
  166     }
  167 
  168     return buffer.slice(index);
  169 };
  170 
  171 // Directory here means the ZIP Central Directory, not a folder
  172 DecompressZip.prototype.readDirectory = function (recordBuffer) {
  173     var record = structures.readEndRecord(recordBuffer);
  174 
  175     return this.getBuffer(record.directoryOffset, record.directoryOffset + record.directorySize)
  176     .then(structures.readDirectory.bind(null));
  177 };
  178 
  179 DecompressZip.prototype.getFiles = function () {
  180     if (!this._p.getFiles) {
  181         this._p.getFiles = this.openFile()
  182         .then(this.statFile.bind(this))
  183         .then(this.getSearchBuffer.bind(this))
  184         .then(this.findEndOfDirectory.bind(this))
  185         .then(this.readDirectory.bind(this))
  186         .then(this.readFileEntries.bind(this));
  187     }
  188 
  189     return this._p.getFiles;
  190 };
  191 
  192 DecompressZip.prototype.readFileEntries = function (directory) {
  193     var promises = [];
  194     var files = [];
  195     var self = this;
  196 
  197     directory.forEach(function (directoryEntry, index) {
  198         var start = directoryEntry.relativeOffsetOfLocalHeader;
  199         var end = Math.min(self.stats.size, start + structures.maxFileEntrySize);
  200         var fileDetails = new FileDetails(directoryEntry);
  201 
  202         var promise = self.getBuffer(start, end)
  203         .then(structures.readFileEntry.bind(null))
  204         .then(function (fileEntry) {
  205             var maxSize;
  206 
  207             if (fileDetails.compressedSize > 0) {
  208                 maxSize = fileDetails.compressedSize;
  209             } else {
  210                 maxSize = self.stats.size;
  211 
  212                 if (index < directory.length - 1) {
  213                     maxSize = directory[index + 1].relativeOffsetOfLocalHeader;
  214                 }
  215 
  216                 maxSize -= start + fileEntry.entryLength;
  217             }
  218 
  219             fileDetails._offset = start + fileEntry.entryLength;
  220             fileDetails._maxSize = maxSize;
  221 
  222             self.emit('file', fileDetails);
  223             files[index] = fileDetails;
  224         });
  225 
  226         promises.push(promise);
  227     });
  228 
  229     return Q.all(promises)
  230     .then(function () {
  231         return files;
  232     });
  233 };
  234 
  235 DecompressZip.prototype.extractFiles = function (files, options, results) {
  236     var promises = [];
  237     var self = this;
  238 
  239     results = results || [];
  240     var fileIndex = 0;
  241     files.forEach(function (file) {
  242         var promise = self.extractFile(file, options)
  243         .then(function (result) {
  244             self.emit('progress', fileIndex++, files.length);
  245             results.push(result);
  246         });
  247 
  248         promises.push(promise);
  249     });
  250 
  251     return Q.all(promises)
  252     .then(function () {
  253         return results;
  254     });
  255 };
  256 
  257 DecompressZip.prototype.extractFile = function (file, options) {
  258     var destination = path.join(options.path, file.path);
  259 
  260     // Possible compression methods:
  261     //    0 - The file is stored (no compression)
  262     //    1 - The file is Shrunk
  263     //    2 - The file is Reduced with compression factor 1
  264     //    3 - The file is Reduced with compression factor 2
  265     //    4 - The file is Reduced with compression factor 3
  266     //    5 - The file is Reduced with compression factor 4
  267     //    6 - The file is Imploded
  268     //    7 - Reserved for Tokenizing compression algorithm
  269     //    8 - The file is Deflated
  270     //    9 - Enhanced Deflating using Deflate64(tm)
  271     //   10 - PKWARE Data Compression Library Imploding (old IBM TERSE)
  272     //   11 - Reserved by PKWARE
  273     //   12 - File is compressed using BZIP2 algorithm
  274     //   13 - Reserved by PKWARE
  275     //   14 - LZMA (EFS)
  276     //   15 - Reserved by PKWARE
  277     //   16 - Reserved by PKWARE
  278     //   17 - Reserved by PKWARE
  279     //   18 - File is compressed using IBM TERSE (new)
  280     //   19 - IBM LZ77 z Architecture (PFS)
  281     //   97 - WavPack compressed data
  282     //   98 - PPMd version I, Rev 1
  283 
  284     if (file.type === 'Directory') {
  285         return extractors.folder(file, destination, this);
  286     }
  287 
  288     if (file.type === 'File') {
  289         switch (file.compressionMethod) {
  290         case 0:
  291             return extractors.store(file, destination, this);
  292 
  293         case 8:
  294             return extractors.deflate(file, destination, this);
  295 
  296         default:
  297             throw new Error('Unsupported compression type');
  298         }
  299     }
  300 
  301     if (file.type === 'SymbolicLink') {
  302         if (options.follow) {
  303             return extractors.copy(file, destination, this, options.path);
  304         } else {
  305             return extractors.symlink(file, destination, this, options.path);
  306         }
  307     }
  308 
  309     throw new Error('Unsupported file type "' + file.type + '"');
  310 };
  311 
  312 module.exports = DecompressZip;