gzip.js 4.4 KB
Newer Older
P
Peter Pan 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
/* jshint esversion: 6 */
/* global pako */

var gzip = gzip || {};

gzip.Archive = class {

    constructor(buffer) {
        this._entries = [];
        if (buffer.length < 18 || buffer[0] != 0x1f || buffer[1] != 0x8b) {
            throw new gzip.Error('Invalid GZIP archive.');
        }
        const reader = new gzip.Reader(buffer, 0, buffer.length);
        this._entries.push(new gzip.Entry(reader));
    }

    get entries() {
        return this._entries;
    }
};

gzip.Entry = class {

    constructor(reader) {
        if (!reader.match([ 0x1f, 0x8b ])) {
            throw new gzip.Error('Invalid GZIP signature.');
        }
        const compressionMethod = reader.byte();
        if (compressionMethod != 8) {
            throw new gzip.Error("Invalid compression method '" + compressionMethod.toString() + "'.");
        }
        const flags = reader.byte();
        reader.uint32(); // MTIME
        reader.byte();
        reader.byte(); // OS
        if ((flags & 4) != 0) {
            const xlen = reader.uint16();
            reader.skip(xlen);
        }
        if ((flags & 8) != 0) {
            this._name = reader.string();
        }
        if ((flags & 16) != 0) { // FLG.FCOMMENT
            reader.string();
        }
        if ((flags & 1) != 0) {
            reader.uint16(); // CRC16
        }
        const compressedData = reader.bytes();
        if (typeof process === 'object' && typeof process.versions == 'object' && typeof process.versions.node !== 'undefined') {
            this._data = require('zlib').inflateRawSync(compressedData);
        }
        else if (typeof pako !== 'undefined') {
            this._data = pako.inflateRaw(compressedData);
        }
        else {
            this._data = new require('./zip').Inflater().inflateRaw(compressedData);
        }
        reader.position = -8;
        reader.uint32(); // CRC32
        const size = reader.uint32();
        if (size != this._data.length) {
            throw new gzip.Error('Invalid size.');
        }
    }

    get name() {
        return this._name;
    }

    get data() {
        return this._data;
    }

};

gzip.Reader = class {

    constructor(buffer, start, end) {
        this._buffer = buffer;
        this._position = start;
        this._end = end;
    }

    match(signature) {
        if (this._position + signature.length <= this._end) {
            for (let i = 0; i < signature.length; i++) {
                if (this._buffer[this._position + i] != signature[i]) {
                    return false;
                }
            }
        }
        this._position += signature.length;
        return true;
    }

    get position() {
        return this._position;
    }

    set position(value) {
        this._position = value >= 0 ? value : this._end + value;
    }

    skip(size) {
        if (this._position + size > this._end) {
            throw new gzip.Error('Data not available.');
        }
        this._position += size;
    }

    bytes(size) {
        if (this._position + size > this._end) {
            throw new gzip.Error('Data not available.');
        }
        size = size === undefined ? this._end : size;
        const data = this._buffer.subarray(this._position, this._position + size);
        this._position += size;
        return data;
    }

    byte() {
        if (this._position + 1 > this._end) {
            throw new gzip.Error('Data not available.');
        }
        const value = this._buffer[this._position];
        this._position++;
        return value;
    }

    uint16() {
        if (this._position + 2 > this._end) {
            throw new gzip.Error('Data not available.');
        }
        const value = this._buffer[this._position] | (this._buffer[this._position + 1] << 8);
        this._position += 2;
        return value;
    }

    uint32() {
        return this.uint16() | (this.uint16() << 16);
    }

    string() {
        let result = '';
        const end = this._buffer.indexOf(0x00, this._position);
        if (end < 0) {
            throw new gzip.Error('End of string not found.');
        }
        while (this._position < end) {
            result += String.fromCharCode(this._buffer[this._position++]);
        }
        this._position++;
        return result;
    }

};

gzip.Error = class extends Error {
    constructor(message) {
        super(message);
        this.name = 'gzip Error';
    }
};

if (typeof module !== 'undefined' && typeof module.exports === 'object') {
    module.exports.Archive = gzip.Archive;
}