CoolFace
Apppublic

TrinetraLabs/Placebo_AI

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes
ZipFileWorker.js540 linesDownload Raw Back to generate
1"use strict";2 3var utils = require("../utils");4var GenericWorker = require("../stream/GenericWorker");5var utf8 = require("../utf8");6var crc32 = require("../crc32");7var signature = require("../signature");8 9/**10 * Transform an integer into a string in hexadecimal.11 * @private12 * @param {number} dec the number to convert.13 * @param {number} bytes the number of bytes to generate.14 * @returns {string} the result.15 */16var decToHex = function(dec, bytes) {17    var hex = "", i;18    for (i = 0; i < bytes; i++) {19        hex += String.fromCharCode(dec & 0xff);20        dec = dec >>> 8;21    }22    return hex;23};24 25/**26 * Generate the UNIX part of the external file attributes.27 * @param {Object} unixPermissions the unix permissions or null.28 * @param {Boolean} isDir true if the entry is a directory, false otherwise.29 * @return {Number} a 32 bit integer.30 *31 * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute :32 *33 * TTTTsstrwxrwxrwx0000000000ADVSHR34 * ^^^^____________________________ file type, see zipinfo.c (UNX_*)35 *     ^^^_________________________ setuid, setgid, sticky36 *        ^^^^^^^^^________________ permissions37 *                 ^^^^^^^^^^______ not used ?38 *                           ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only39 */40var generateUnixExternalFileAttr = function (unixPermissions, isDir) {41 42    var result = unixPermissions;43    if (!unixPermissions) {44        // I can't use octal values in strict mode, hence the hexa.45        //  040775 => 0x41fd46        // 0100664 => 0x81b447        result = isDir ? 0x41fd : 0x81b4;48    }49    return (result & 0xFFFF) << 16;50};51 52/**53 * Generate the DOS part of the external file attributes.54 * @param {Object} dosPermissions the dos permissions or null.55 * @param {Boolean} isDir true if the entry is a directory, false otherwise.56 * @return {Number} a 32 bit integer.57 *58 * Bit 0     Read-Only59 * Bit 1     Hidden60 * Bit 2     System61 * Bit 3     Volume Label62 * Bit 4     Directory63 * Bit 5     Archive64 */65var generateDosExternalFileAttr = function (dosPermissions) {66    // the dir flag is already set for compatibility67    return (dosPermissions || 0)  & 0x3F;68};69 70/**71 * Generate the various parts used in the construction of the final zip file.72 * @param {Object} streamInfo the hash with information about the compressed file.73 * @param {Boolean} streamedContent is the content streamed ?74 * @param {Boolean} streamingEnded is the stream finished ?75 * @param {number} offset the current offset from the start of the zip file.76 * @param {String} platform let's pretend we are this platform (change platform dependents fields)77 * @param {Function} encodeFileName the function to encode the file name / comment.78 * @return {Object} the zip parts.79 */80var generateZipParts = function(streamInfo, streamedContent, streamingEnded, offset, platform, encodeFileName) {81    var file = streamInfo["file"],82        compression = streamInfo["compression"],83        useCustomEncoding = encodeFileName !== utf8.utf8encode,84        encodedFileName = utils.transformTo("string", encodeFileName(file.name)),85        utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)),86        comment = file.comment,87        encodedComment = utils.transformTo("string", encodeFileName(comment)),88        utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)),89        useUTF8ForFileName = utfEncodedFileName.length !== file.name.length,90        useUTF8ForComment = utfEncodedComment.length !== comment.length,91        dosTime,92        dosDate,93        extraFields = "",94        unicodePathExtraField = "",95        unicodeCommentExtraField = "",96        dir = file.dir,97        date = file.date;98 99 100    var dataInfo = {101        crc32 : 0,102        compressedSize : 0,103        uncompressedSize : 0104    };105 106    // if the content is streamed, the sizes/crc32 are only available AFTER107    // the end of the stream.108    if (!streamedContent || streamingEnded) {109        dataInfo.crc32 = streamInfo["crc32"];110        dataInfo.compressedSize = streamInfo["compressedSize"];111        dataInfo.uncompressedSize = streamInfo["uncompressedSize"];112    }113 114    var bitflag = 0;115    if (streamedContent) {116        // Bit 3: the sizes/crc32 are set to zero in the local header.117        // The correct values are put in the data descriptor immediately118        // following the compressed data.119        bitflag |= 0x0008;120    }121    if (!useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment)) {122        // Bit 11: Language encoding flag (EFS).123        bitflag |= 0x0800;124    }125 126 127    var extFileAttr = 0;128    var versionMadeBy = 0;129    if (dir) {130        // dos or unix, we set the dos dir flag131        extFileAttr |= 0x00010;132    }133    if(platform === "UNIX") {134        versionMadeBy = 0x031E; // UNIX, version 3.0135        extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir);136    } else { // DOS or other, fallback to DOS137        versionMadeBy = 0x0014; // DOS, version 2.0138        extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir);139    }140 141    // date142    // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html143    // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html144    // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html145 146    dosTime = date.getUTCHours();147    dosTime = dosTime << 6;148    dosTime = dosTime | date.getUTCMinutes();149    dosTime = dosTime << 5;150    dosTime = dosTime | date.getUTCSeconds() / 2;151 152    dosDate = date.getUTCFullYear() - 1980;153    dosDate = dosDate << 4;154    dosDate = dosDate | (date.getUTCMonth() + 1);155    dosDate = dosDate << 5;156    dosDate = dosDate | date.getUTCDate();157 158    if (useUTF8ForFileName) {159        // set the unicode path extra field. unzip needs at least one extra160        // field to correctly handle unicode path, so using the path is as good161        // as any other information. This could improve the situation with162        // other archive managers too.163        // This field is usually used without the utf8 flag, with a non164        // unicode path in the header (winrar, winzip). This helps (a bit)165        // with the messy Windows' default compressed folders feature but166        // breaks on p7zip which doesn't seek the unicode path extra field.167        // So for now, UTF-8 everywhere !168        unicodePathExtraField =169            // Version170            decToHex(1, 1) +171            // NameCRC32172            decToHex(crc32(encodedFileName), 4) +173            // UnicodeName174            utfEncodedFileName;175 176        extraFields +=177            // Info-ZIP Unicode Path Extra Field178            "\x75\x70" +179            // size180            decToHex(unicodePathExtraField.length, 2) +181            // content182            unicodePathExtraField;183    }184 185    if(useUTF8ForComment) {186 187        unicodeCommentExtraField =188            // Version189            decToHex(1, 1) +190            // CommentCRC32191            decToHex(crc32(encodedComment), 4) +192            // UnicodeName193            utfEncodedComment;194 195        extraFields +=196            // Info-ZIP Unicode Path Extra Field197            "\x75\x63" +198            // size199            decToHex(unicodeCommentExtraField.length, 2) +200            // content201            unicodeCommentExtraField;202    }203 204    var header = "";205 206    // version needed to extract207    header += "\x0A\x00";208    // general purpose bit flag209    header += decToHex(bitflag, 2);210    // compression method211    header += compression.magic;212    // last mod file time213    header += decToHex(dosTime, 2);214    // last mod file date215    header += decToHex(dosDate, 2);216    // crc-32217    header += decToHex(dataInfo.crc32, 4);218    // compressed size219    header += decToHex(dataInfo.compressedSize, 4);220    // uncompressed size221    header += decToHex(dataInfo.uncompressedSize, 4);222    // file name length223    header += decToHex(encodedFileName.length, 2);224    // extra field length225    header += decToHex(extraFields.length, 2);226 227 228    var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields;229 230    var dirRecord = signature.CENTRAL_FILE_HEADER +231        // version made by (00: DOS)232        decToHex(versionMadeBy, 2) +233        // file header (common to file and central directory)234        header +235        // file comment length236        decToHex(encodedComment.length, 2) +237        // disk number start238        "\x00\x00" +239        // internal file attributes TODO240        "\x00\x00" +241        // external file attributes242        decToHex(extFileAttr, 4) +243        // relative offset of local header244        decToHex(offset, 4) +245        // file name246        encodedFileName +247        // extra field248        extraFields +249        // file comment250        encodedComment;251 252    return {253        fileRecord: fileRecord,254        dirRecord: dirRecord255    };256};257 258/**259 * Generate the EOCD record.260 * @param {Number} entriesCount the number of entries in the zip file.261 * @param {Number} centralDirLength the length (in bytes) of the central dir.262 * @param {Number} localDirLength the length (in bytes) of the local dir.263 * @param {String} comment the zip file comment as a binary string.264 * @param {Function} encodeFileName the function to encode the comment.265 * @return {String} the EOCD record.266 */267var generateCentralDirectoryEnd = function (entriesCount, centralDirLength, localDirLength, comment, encodeFileName) {268    var dirEnd = "";269    var encodedComment = utils.transformTo("string", encodeFileName(comment));270 271    // end of central dir signature272    dirEnd = signature.CENTRAL_DIRECTORY_END +273        // number of this disk274        "\x00\x00" +275        // number of the disk with the start of the central directory276        "\x00\x00" +277        // total number of entries in the central directory on this disk278        decToHex(entriesCount, 2) +279        // total number of entries in the central directory280        decToHex(entriesCount, 2) +281        // size of the central directory   4 bytes282        decToHex(centralDirLength, 4) +283        // offset of start of central directory with respect to the starting disk number284        decToHex(localDirLength, 4) +285        // .ZIP file comment length286        decToHex(encodedComment.length, 2) +287        // .ZIP file comment288        encodedComment;289 290    return dirEnd;291};292 293/**294 * Generate data descriptors for a file entry.295 * @param {Object} streamInfo the hash generated by a worker, containing information296 * on the file entry.297 * @return {String} the data descriptors.298 */299var generateDataDescriptors = function (streamInfo) {300    var descriptor = "";301    descriptor = signature.DATA_DESCRIPTOR +302        // crc-32                          4 bytes303        decToHex(streamInfo["crc32"], 4) +304        // compressed size                 4 bytes305        decToHex(streamInfo["compressedSize"], 4) +306        // uncompressed size               4 bytes307        decToHex(streamInfo["uncompressedSize"], 4);308 309    return descriptor;310};311 312 313/**314 * A worker to concatenate other workers to create a zip file.315 * @param {Boolean} streamFiles `true` to stream the content of the files,316 * `false` to accumulate it.317 * @param {String} comment the comment to use.318 * @param {String} platform the platform to use, "UNIX" or "DOS".319 * @param {Function} encodeFileName the function to encode file names and comments.320 */321function ZipFileWorker(streamFiles, comment, platform, encodeFileName) {322    GenericWorker.call(this, "ZipFileWorker");323    // The number of bytes written so far. This doesn't count accumulated chunks.324    this.bytesWritten = 0;325    // The comment of the zip file326    this.zipComment = comment;327    // The platform "generating" the zip file.328    this.zipPlatform = platform;329    // the function to encode file names and comments.330    this.encodeFileName = encodeFileName;331    // Should we stream the content of the files ?332    this.streamFiles = streamFiles;333    // If `streamFiles` is false, we will need to accumulate the content of the334    // files to calculate sizes / crc32 (and write them *before* the content).335    // This boolean indicates if we are accumulating chunks (it will change a lot336    // during the lifetime of this worker).337    this.accumulate = false;338    // The buffer receiving chunks when accumulating content.339    this.contentBuffer = [];340    // The list of generated directory records.341    this.dirRecords = [];342    // The offset (in bytes) from the beginning of the zip file for the current source.343    this.currentSourceOffset = 0;344    // The total number of entries in this zip file.345    this.entriesCount = 0;346    // the name of the file currently being added, null when handling the end of the zip file.347    // Used for the emitted metadata.348    this.currentFile = null;349 350 351 352    this._sources = [];353}354utils.inherits(ZipFileWorker, GenericWorker);355 356/**357 * @see GenericWorker.push358 */359ZipFileWorker.prototype.push = function (chunk) {360 361    var currentFilePercent = chunk.meta.percent || 0;362    var entriesCount = this.entriesCount;363    var remainingFiles = this._sources.length;364 365    if(this.accumulate) {366        this.contentBuffer.push(chunk);367    } else {368        this.bytesWritten += chunk.data.length;369 370        GenericWorker.prototype.push.call(this, {371            data : chunk.data,372            meta : {373                currentFile : this.currentFile,374                percent : entriesCount ? (currentFilePercent + 100 * (entriesCount - remainingFiles - 1)) / entriesCount : 100375            }376        });377    }378};379 380/**381 * The worker started a new source (an other worker).382 * @param {Object} streamInfo the streamInfo object from the new source.383 */384ZipFileWorker.prototype.openedSource = function (streamInfo) {385    this.currentSourceOffset = this.bytesWritten;386    this.currentFile = streamInfo["file"].name;387 388    var streamedContent = this.streamFiles && !streamInfo["file"].dir;389 390    // don't stream folders (because they don't have any content)391    if(streamedContent) {392        var record = generateZipParts(streamInfo, streamedContent, false, this.currentSourceOffset, this.zipPlatform, this.encodeFileName);393        this.push({394            data : record.fileRecord,395            meta : {percent:0}396        });397    } else {398        // we need to wait for the whole file before pushing anything399        this.accumulate = true;400    }401};402 403/**404 * The worker finished a source (an other worker).405 * @param {Object} streamInfo the streamInfo object from the finished source.406 */407ZipFileWorker.prototype.closedSource = function (streamInfo) {408    this.accumulate = false;409    var streamedContent = this.streamFiles && !streamInfo["file"].dir;410    var record = generateZipParts(streamInfo, streamedContent, true, this.currentSourceOffset, this.zipPlatform, this.encodeFileName);411 412    this.dirRecords.push(record.dirRecord);413    if(streamedContent) {414        // after the streamed file, we put data descriptors415        this.push({416            data : generateDataDescriptors(streamInfo),417            meta : {percent:100}418        });419    } else {420        // the content wasn't streamed, we need to push everything now421        // first the file record, then the content422        this.push({423            data : record.fileRecord,424            meta : {percent:0}425        });426        while(this.contentBuffer.length) {427            this.push(this.contentBuffer.shift());428        }429    }430    this.currentFile = null;431};432 433/**434 * @see GenericWorker.flush435 */436ZipFileWorker.prototype.flush = function () {437 438    var localDirLength = this.bytesWritten;439    for(var i = 0; i < this.dirRecords.length; i++) {440        this.push({441            data : this.dirRecords[i],442            meta : {percent:100}443        });444    }445    var centralDirLength = this.bytesWritten - localDirLength;446 447    var dirEnd = generateCentralDirectoryEnd(this.dirRecords.length, centralDirLength, localDirLength, this.zipComment, this.encodeFileName);448 449    this.push({450        data : dirEnd,451        meta : {percent:100}452    });453};454 455/**456 * Prepare the next source to be read.457 */458ZipFileWorker.prototype.prepareNextSource = function () {459    this.previous = this._sources.shift();460    this.openedSource(this.previous.streamInfo);461    if (this.isPaused) {462        this.previous.pause();463    } else {464        this.previous.resume();465    }466};467 468/**469 * @see GenericWorker.registerPrevious470 */471ZipFileWorker.prototype.registerPrevious = function (previous) {472    this._sources.push(previous);473    var self = this;474 475    previous.on("data", function (chunk) {476        self.processChunk(chunk);477    });478    previous.on("end", function () {479        self.closedSource(self.previous.streamInfo);480        if(self._sources.length) {481            self.prepareNextSource();482        } else {483            self.end();484        }485    });486    previous.on("error", function (e) {487        self.error(e);488    });489    return this;490};491 492/**493 * @see GenericWorker.resume494 */495ZipFileWorker.prototype.resume = function () {496    if(!GenericWorker.prototype.resume.call(this)) {497        return false;498    }499 500    if (!this.previous && this._sources.length) {501        this.prepareNextSource();502        return true;503    }504    if (!this.previous && !this._sources.length && !this.generatedError) {505        this.end();506        return true;507    }508};509 510/**511 * @see GenericWorker.error512 */513ZipFileWorker.prototype.error = function (e) {514    var sources = this._sources;515    if(!GenericWorker.prototype.error.call(this, e)) {516        return false;517    }518    for(var i = 0; i < sources.length; i++) {519        try {520            sources[i].error(e);521        } catch(e) {522            // the `error` exploded, nothing to do523        }524    }525    return true;526};527 528/**529 * @see GenericWorker.lock530 */531ZipFileWorker.prototype.lock = function () {532    GenericWorker.prototype.lock.call(this);533    var sources = this._sources;534    for(var i = 0; i < sources.length; i++) {535        sources[i].lock();536    }537};538 539module.exports = ZipFileWorker;540