You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

234 lines
6.1 KiB
JavaScript

var gzip = gzip || {};
var zip = zip || require('./zip');
gzip.Archive = class {
static open(data) {
const stream = data instanceof Uint8Array ? new gzip.BinaryReader(data) : data;
const signature = [ 0x1f, 0x8b ];
if (stream.length > 18 && stream.peek(2).every((value, index) => value === signature[index])) {
return new gzip.Archive(stream);
}
return null;
}
constructor(stream) {
const position = stream.position;
const entry = new gzip.Entry(stream);
this._entries = new Map([ [ entry.name, entry.stream ] ]);
stream.seek(position);
}
get entries() {
return this._entries;
}
};
gzip.Entry = class {
constructor(stream) {
const signature = [ 0x1f, 0x8b ];
if (stream.position + 2 > stream.length ||
!stream.read(2).every((value, index) => value === signature[index])) {
throw new gzip.Error('Invalid gzip signature.');
}
const string = () => {
let content = '';
while (stream.position < stream.length) {
const value = stream.byte();
if (value === 0x00) {
break;
}
content += String.fromCharCode(value);
}
return content;
};
const reader = new gzip.BinaryReader(stream.read(8));
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(); // XFL
reader.byte(); // OS
if ((flags & 4) != 0) { // FEXTRA
const xlen = stream.byte() | (stream.byte() << 8);
stream.skip(xlen);
}
this._name = (flags & 8) != 0 ? string() : ''; // FNAME
if ((flags & 16) != 0) { // FCOMMENT
string();
}
if ((flags & 1) != 0) { // FHCRC
stream.skip(2);
}
this._stream = new gzip.InflaterStream(stream);
}
get name() {
return this._name;
}
get stream() {
return this._stream;
}
};
gzip.InflaterStream = class {
constructor(stream) {
this._stream = stream.stream(stream.length - stream.position - 8);
const reader = new gzip.BinaryReader(stream.read(8));
reader.uint32(); // CRC32
this._length = reader.uint32(); // ISIZE
this._position = 0;
}
get position() {
return this._position;
}
get length() {
return this._length;
}
seek(position) {
if (this._buffer === undefined) {
this._inflate();
}
this._position = position >= 0 ? position : this._length + position;
}
skip(offset) {
if (this._buffer === undefined) {
this._inflate();
}
this._position += offset;
}
stream(length) {
return new gzip.BinaryReader(this.read(length));
}
peek(length) {
const position = this._position;
length = length !== undefined ? length : this._length - this._position;
this.skip(length);
const end = this._position;
this.seek(position);
if (position === 0 && length === this._length) {
return this._buffer;
}
return this._buffer.subarray(position, end);
}
read(length) {
const position = this._position;
length = length !== undefined ? length : this._length - this._position;
this.skip(length);
if (position === 0 && length === this._length) {
return this._buffer;
}
return this._buffer.subarray(position, this._position);
}
byte() {
const position = this._position;
this.skip(1);
return this._buffer[position];
}
_inflate() {
if (this._buffer === undefined) {
const buffer = this._stream.peek();
this._buffer = new zip.Inflater().inflateRaw(buffer, this._length);
delete this._stream;
}
}
};
gzip.BinaryReader = class {
constructor(buffer) {
this._buffer = buffer;
this._length = buffer.length;
this._position = 0;
this._view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
}
get position() {
return this._position;
}
get length() {
return this._length;
}
create(buffer) {
return new gzip.BinaryReader(buffer);
}
stream(length) {
return this.create(this.read(length));
}
seek(position) {
this._position = position >= 0 ? position : this._length + position;
}
skip(offset) {
this._position += offset;
}
peek(length) {
if (this._position === 0 && length === undefined) {
return this._buffer;
}
const position = this._position;
this.skip(length !== undefined ? length : this._length - this._position);
const end = this._position;
this.seek(position);
return this._buffer.subarray(position, end);
}
read(length) {
if (this._position === 0 && length === undefined) {
this._position = this._length;
return this._buffer;
}
const position = this._position;
this.skip(length !== undefined ? length : this._length - this._position);
return this._buffer.subarray(position, this._position);
}
byte() {
const position = this._position;
this.skip(1);
return this._buffer[position];
}
uint16() {
const position = this._position;
this.skip(2);
return this._view.getUint16(position, true);
}
uint32() {
const position = this._position;
this.skip(4);
return this._view.getUint32(position, true);
}
};
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;
}