Commit a159bb0b authored by OzzieIsaacs's avatar OzzieIsaacs

Code Csmetics kthoom integration

parent 5b9b36f9
...@@ -13,341 +13,342 @@ bitjs.archive = bitjs.archive || {}; ...@@ -13,341 +13,342 @@ bitjs.archive = bitjs.archive || {};
(function() { (function() {
// =========================================================================== // ===========================================================================
// Stolen from Closure because it's the best way to do Java-like inheritance. // Stolen from Closure because it's the best way to do Java-like inheritance.
bitjs.base = function(me, opt_methodName, var_args) { bitjs.base = function(me, opt_methodName, var_args) {
var caller = arguments.callee.caller; var caller = arguments.callee.caller;
if (caller.superClass_) { if (caller.superClass_) {
// This is a constructor. Call the superclass constructor. // This is a constructor. Call the superclass constructor.
return caller.superClass_.constructor.apply( return caller.superClass_.constructor.apply(
me, Array.prototype.slice.call(arguments, 1)); me, Array.prototype.slice.call(arguments, 1));
} }
var args = Array.prototype.slice.call(arguments, 2); var args = Array.prototype.slice.call(arguments, 2);
var foundCaller = false; var foundCaller = false;
for (var ctor = me.constructor; for (var ctor = me.constructor;
ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) { ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
if (ctor.prototype[opt_methodName] === caller) { if (ctor.prototype[opt_methodName] === caller) {
foundCaller = true; foundCaller = true;
} else if (foundCaller) { } else if (foundCaller) {
return ctor.prototype[opt_methodName].apply(me, args); return ctor.prototype[opt_methodName].apply(me, args);
} }
} }
// If we did not find the caller in the prototype chain, // If we did not find the caller in the prototype chain,
// then one of two things happened: // then one of two things happened:
// 1) The caller is an instance method. // 1) The caller is an instance method.
// 2) This method was not called by the right caller. // 2) This method was not called by the right caller.
if (me[opt_methodName] === caller) { if (me[opt_methodName] === caller) {
return me.constructor.prototype[opt_methodName].apply(me, args); return me.constructor.prototype[opt_methodName].apply(me, args);
} else { } else {
throw Error( throw Error(
'goog.base called from a method of one name ' + 'goog.base called from a method of one name ' +
'to a method of a different name'); 'to a method of a different name');
} }
}; };
bitjs.inherits = function(childCtor, parentCtor) { bitjs.inherits = function(childCtor, parentCtor) {
/** @constructor */ /** @constructor */
function tempCtor() {}; function tempCtor() {};
tempCtor.prototype = parentCtor.prototype; tempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype; childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new tempCtor(); childCtor.prototype = new tempCtor();
childCtor.prototype.constructor = childCtor; childCtor.prototype.constructor = childCtor;
}; };
// =========================================================================== // ===========================================================================
/** /**
* An unarchive event. * An unarchive event.
* *
* @param {string} type The event type. * @param {string} type The event type.
* @constructor * @constructor
*/ */
bitjs.archive.UnarchiveEvent = function(type) { bitjs.archive.UnarchiveEvent = function(type) {
/** /**
* The event type. * The event type.
* *
* @type {string} * @type {string}
*/ */
this.type = type; this.type = type;
}; };
/**
* The UnarchiveEvent types.
*/
bitjs.archive.UnarchiveEvent.Type = {
START: 'start',
PROGRESS: 'progress',
EXTRACT: 'extract',
FINISH: 'finish',
INFO: 'info',
ERROR: 'error'
};
/**
* Useful for passing info up to the client (for debugging).
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveInfoEvent = function(msg) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
/**
* The information message.
*
* @type {string}
*/
this.msg = msg;
};
bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
/**
* An unrecoverable error has occured.
*
* @param {string} msg The error message.
*/
bitjs.archive.UnarchiveErrorEvent = function(msg) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
/**
* The information message.
*
* @type {string}
*/
this.msg = msg;
};
bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
/**
* Start event.
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveStartEvent = function() {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.START);
};
bitjs.inherits(bitjs.archive.UnarchiveStartEvent, bitjs.archive.UnarchiveEvent);
/**
* Finish event.
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveFinishEvent = function() {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.FINISH);
};
bitjs.inherits(bitjs.archive.UnarchiveFinishEvent, bitjs.archive.UnarchiveEvent);
/**
* Progress event.
*/
bitjs.archive.UnarchiveProgressEvent = function(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.PROGRESS);
this.currentFilename = currentFilename;
this.currentFileNumber = currentFileNumber;
this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile;
this.totalFilesInArchive = totalFilesInArchive;
this.currentBytesUnarchived = currentBytesUnarchived;
this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive;
};
bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEvent);
/**
* All extracted files returned by an Unarchiver will implement
* the following interface:
*
* interface UnarchivedFile {
* string filename
* TypedArray fileData
* }
*
*/
/**
* Extract event.
*/
bitjs.archive.UnarchiveExtractEvent = function(unarchivedFile) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
/**
* @type {UnarchivedFile}
*/
this.unarchivedFile = unarchivedFile;
};
bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
/**
* Base class for all Unarchivers.
*
* @param {ArrayBuffer} arrayBuffer The Array Buffer.
* @param {string} opt_pathToBitJS Optional string for where the BitJS files are located.
* @constructor
*/
bitjs.archive.Unarchiver = function(arrayBuffer, opt_pathToBitJS) {
/**
* The ArrayBuffer object.
* @type {ArrayBuffer}
* @protected
*/
this.ab = arrayBuffer;
/**
* The path to the BitJS files.
* @type {string}
* @private
*/
this.pathToBitJS_ = opt_pathToBitJS || '';
/**
* A map from event type to an array of listeners.
* @type {Map.<string, Array>}
*/
this.listeners_ = {};
for (var type in bitjs.archive.UnarchiveEvent.Type) {
this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
}
};
/**
* Private web worker initialized during start().
* @type {Worker}
* @private
*/
bitjs.archive.Unarchiver.prototype.worker_ = null;
/**
* This method must be overridden by the subclass to return the script filename.
* @return {string} The script filename.
* @protected.
*/
bitjs.archive.Unarchiver.prototype.getScriptFileName = function() {
throw 'Subclasses of AbstractUnarchiver must overload getScriptFileName()';
};
/**
* Adds an event listener for UnarchiveEvents.
*
* @param {string} Event type.
* @param {function} An event handler function.
*/
bitjs.archive.Unarchiver.prototype.addEventListener = function(type, listener) {
if (type in this.listeners_) {
if (this.listeners_[type].indexOf(listener) == -1) {
this.listeners_[type].push(listener);
}
}
};
/** /**
* Removes an event listener. * The UnarchiveEvent types.
* */
* @param {string} Event type. bitjs.archive.UnarchiveEvent.Type = {
* @param {EventListener|function} An event listener or handler function. START: 'start',
*/ PROGRESS: 'progress',
bitjs.archive.Unarchiver.prototype.removeEventListener = function(type, listener) { EXTRACT: 'extract',
if (type in this.listeners_) { FINISH: 'finish',
var index = this.listeners_[type].indexOf(listener); INFO: 'info',
if (index != -1) { ERROR: 'error'
this.listeners_[type].splice(index, 1); };
}
}
};
/** /**
* Receive an event and pass it to the listener functions. * Useful for passing info up to the client (for debugging).
* *
* @param {bitjs.archive.UnarchiveEvent} e * @param {string} msg The info message.
* @private */
*/ bitjs.archive.UnarchiveInfoEvent = function(msg) {
bitjs.archive.Unarchiver.prototype.handleWorkerEvent_ = function(e) { bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) &&
this.listeners_[e.type] instanceof Array) { /**
this.listeners_[e.type].forEach(function (listener) { listener(e) }); * The information message.
if (e.type == bitjs.archive.UnarchiveEvent.Type.FINISH) { *
this.worker_.terminate(); * @type {string}
} */
} else { this.msg = msg;
console.log(e); };
} bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
};
/**
* An unrecoverable error has occured.
*
* @param {string} msg The error message.
*/
bitjs.archive.UnarchiveErrorEvent = function(msg) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
/**
* The information message.
*
* @type {string}
*/
this.msg = msg;
};
bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
/**
* Start event.
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveStartEvent = function() {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.START);
};
bitjs.inherits(bitjs.archive.UnarchiveStartEvent, bitjs.archive.UnarchiveEvent);
/**
* Finish event.
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveFinishEvent = function() {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.FINISH);
};
bitjs.inherits(bitjs.archive.UnarchiveFinishEvent, bitjs.archive.UnarchiveEvent);
/**
* Progress event.
*/
bitjs.archive.UnarchiveProgressEvent = function(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive)
{
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.PROGRESS);
this.currentFilename = currentFilename;
this.currentFileNumber = currentFileNumber;
this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile;
this.totalFilesInArchive = totalFilesInArchive;
this.currentBytesUnarchived = currentBytesUnarchived;
this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive;
};
bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEvent);
/**
* All extracted files returned by an Unarchiver will implement
* the following interface:
*
* interface UnarchivedFile {
* string filename
* TypedArray fileData
* }
*
*/
/**
* Extract event.
*/
bitjs.archive.UnarchiveExtractEvent = function(unarchivedFile) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
/**
* @type {UnarchivedFile}
*/
this.unarchivedFile = unarchivedFile;
};
bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
/**
* Base class for all Unarchivers.
*
* @param {ArrayBuffer} arrayBuffer The Array Buffer.
* @param {string} opt_pathToBitJS Optional string for where the BitJS files are located.
* @constructor
*/
bitjs.archive.Unarchiver = function(arrayBuffer, opt_pathToBitJS) {
/**
* The ArrayBuffer object.
* @type {ArrayBuffer}
* @protected
*/
this.ab = arrayBuffer;
/**
* The path to the BitJS files.
* @type {string}
* @private
*/
this.pathToBitJS_ = opt_pathToBitJS || '';
/**
* A map from event type to an array of listeners.
* @type {Map.<string, Array>}
*/
this.listeners_ = {};
for (var type in bitjs.archive.UnarchiveEvent.Type) {
this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
}
};
/** /**
* Starts the unarchive in a separate Web Worker thread and returns immediately. * Private web worker initialized during start().
*/ * @type {Worker}
bitjs.archive.Unarchiver.prototype.start = function() { * @private
var me = this; */
var scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); bitjs.archive.Unarchiver.prototype.worker_ = null;
if (scriptFileName) {
this.worker_ = new Worker(scriptFileName); /**
* This method must be overridden by the subclass to return the script filename.
this.worker_.onerror = function(e) { * @return {string} The script filename.
console.log('Worker error: message = ' + e.message); * @protected.
throw e; */
bitjs.archive.Unarchiver.prototype.getScriptFileName = function() {
throw 'Subclasses of AbstractUnarchiver must overload getScriptFileName()';
}; };
this.worker_.onmessage = function(e) { /**
if (typeof e.data == 'string') { * Adds an event listener for UnarchiveEvents.
// Just log any strings the workers pump our way. *
console.log(e.data); * @param {string} Event type.
} else { * @param {function} An event handler function.
// Assume that it is an UnarchiveEvent. Some browsers preserve the 'type' */
// so that instanceof UnarchiveEvent returns true, but others do not. bitjs.archive.Unarchiver.prototype.addEventListener = function(type, listener) {
me.handleWorkerEvent_(e.data); if (type in this.listeners_) {
} if (this.listeners_[type].indexOf(listener) == -1) {
this.listeners_[type].push(listener);
}
}
}; };
this.worker_.postMessage({file: this.ab}); /**
} * Removes an event listener.
}; *
* @param {string} Event type.
* @param {EventListener|function} An event listener or handler function.
*/
bitjs.archive.Unarchiver.prototype.removeEventListener = function(type, listener) {
if (type in this.listeners_) {
var index = this.listeners_[type].indexOf(listener);
if (index != -1) {
this.listeners_[type].splice(index, 1);
}
}
};
/** /**
* Terminates the Web Worker for this Unarchiver and returns immediately. * Receive an event and pass it to the listener functions.
*/ *
bitjs.archive.Unarchiver.prototype.stop = function() { * @param {bitjs.archive.UnarchiveEvent} e
if (this.worker_) { * @private
this.worker_.terminate(); */
} bitjs.archive.Unarchiver.prototype.handleWorkerEvent_ = function(e) {
}; if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) &&
this.listeners_[e.type] instanceof Array) {
this.listeners_[e.type].forEach(function (listener) { listener(e) });
if (e.type == bitjs.archive.UnarchiveEvent.Type.FINISH) {
this.worker_.terminate();
}
} else {
console.log(e);
}
};
/**
* Starts the unarchive in a separate Web Worker thread and returns immediately.
*/
bitjs.archive.Unarchiver.prototype.start = function() {
var me = this;
var scriptFileName = this.pathToBitJS_ + this.getScriptFileName();
if (scriptFileName) {
this.worker_ = new Worker(scriptFileName);
this.worker_.onerror = function(e) {
console.log('Worker error: message = ' + e.message);
throw e;
};
this.worker_.onmessage = function(e) {
if (typeof e.data == 'string') {
// Just log any strings the workers pump our way.
console.log(e.data);
} else {
// Assume that it is an UnarchiveEvent. Some browsers preserve the 'type'
// so that instanceof UnarchiveEvent returns true, but others do not.
me.handleWorkerEvent_(e.data);
}
};
this.worker_.postMessage({file: this.ab});
}
};
/** /**
* Unzipper * Terminates the Web Worker for this Unarchiver and returns immediately.
* @extends {bitjs.archive.Unarchiver} */
* @constructor bitjs.archive.Unarchiver.prototype.stop = function() {
*/ if (this.worker_) {
bitjs.archive.Unzipper = function(arrayBuffer, opt_pathToBitJS) { this.worker_.terminate();
bitjs.base(this, arrayBuffer, opt_pathToBitJS); }
}; };
bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver);
bitjs.archive.Unzipper.prototype.getScriptFileName = function() { return 'unzip.js' };
/**
* Unrarrer
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Unrarrer = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, opt_pathToBitJS);
};
bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver);
bitjs.archive.Unrarrer.prototype.getScriptFileName = function() { return 'unrar.js' };
/** /**
* Untarrer * Unzipper
* @extends {bitjs.archive.Unarchiver} * @extends {bitjs.archive.Unarchiver}
* @constructor * @constructor
*/ */
bitjs.archive.Untarrer = function(arrayBuffer, opt_pathToBitJS) { bitjs.archive.Unzipper = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, opt_pathToBitJS); bitjs.base(this, arrayBuffer, opt_pathToBitJS);
}; };
bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver); bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver);
bitjs.archive.Untarrer.prototype.getScriptFileName = function() { return 'untar.js' }; bitjs.archive.Unzipper.prototype.getScriptFileName = function() { return 'unzip.js' };
/**
* Unrarrer
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Unrarrer = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, opt_pathToBitJS);
};
bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver);
bitjs.archive.Unrarrer.prototype.getScriptFileName = function() { return 'unrar.js' };
/**
* Untarrer
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Untarrer = function(arrayBuffer, opt_pathToBitJS) {
bitjs.base(this, arrayBuffer, opt_pathToBitJS);
};
bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver);
bitjs.archive.Untarrer.prototype.getScriptFileName = function() { return 'untar.js' };
})(); })();
\ No newline at end of file
...@@ -14,470 +14,469 @@ bitjs.io = bitjs.io || {}; ...@@ -14,470 +14,469 @@ bitjs.io = bitjs.io || {};
(function() { (function() {
// mask for getting the Nth bit (zero-based) // mask for getting the Nth bit (zero-based)
bitjs.BIT = [ 0x01, 0x02, 0x04, 0x08, bitjs.BIT = [ 0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80, 0x10, 0x20, 0x40, 0x80,
0x100, 0x200, 0x400, 0x800, 0x100, 0x200, 0x400, 0x800,
0x1000, 0x2000, 0x4000, 0x8000]; 0x1000, 0x2000, 0x4000, 0x8000];
// mask for getting N number of bits (0-8) // mask for getting N number of bits (0-8)
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ]; var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ];
/** /**
* This bit stream peeks and consumes bits out of a binary stream. * This bit stream peeks and consumes bits out of a binary stream.
* *
* @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
* @param {boolean} rtl Whether the stream reads bits from the byte starting * @param {boolean} rtl Whether the stream reads bits from the byte starting
* from bit 7 to 0 (true) or bit 0 to 7 (false). * from bit 7 to 0 (true) or bit 0 to 7 (false).
* @param {Number} opt_offset The offset into the ArrayBuffer * @param {Number} opt_offset The offset into the ArrayBuffer
* @param {Number} opt_length The length of this BitStream * @param {Number} opt_length The length of this BitStream
*/ */
bitjs.io.BitStream = function(ab, rtl, opt_offset, opt_length) { bitjs.io.BitStream = function(ab, rtl, opt_offset, opt_length) {
if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") { if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
throw "Error! BitArray constructed with an invalid ArrayBuffer object"; throw "Error! BitArray constructed with an invalid ArrayBuffer object";
} }
var offset = opt_offset || 0; var offset = opt_offset || 0;
var length = opt_length || ab.byteLength; var length = opt_length || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length); this.bytes = new Uint8Array(ab, offset, length);
this.bytePtr = 0; // tracks which byte we are on this.bytePtr = 0; // tracks which byte we are on
this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7)
this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr;
}; };
/** /**
* byte0 byte1 byte2 byte3 * byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0 * 7......0 | 7......0 | 7......0 | 7......0
* *
* The bit pointer starts at bit0 of byte0 and moves left until it reaches * The bit pointer starts at bit0 of byte0 and moves left until it reaches
* bit7 of byte0, then jumps to bit0 of byte1, etc. * bit7 of byte0, then jumps to bit0 of byte1, etc.
* @param {number} n The number of bits to peek. * @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false. * @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number. * @return {number} The peeked bits, as an unsigned number.
*/ */
bitjs.io.BitStream.prototype.peekBits_ltr = function(n, movePointers) { bitjs.io.BitStream.prototype.peekBits_ltr = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) { if (n <= 0 || typeof n != typeof 1) {
return 0; return 0;
} }
var movePointers = movePointers || false, var movePointers = movePointers || false,
bytePtr = this.bytePtr, bytePtr = this.bytePtr,
bitPtr = this.bitPtr, bitPtr = this.bitPtr,
result = 0, result = 0,
bitsIn = 0, bitsIn = 0,
bytes = this.bytes; bytes = this.bytes;
// keep going until we have no more bits left to peek at // keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then // TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want. // shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time. // This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) { while (n > 0) {
if (bytePtr >= bytes.length) { if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr; bytes.length + ", bitPtr=" + bitPtr;
return -1; return -1;
} }
var numBitsLeftInThisByte = (8 - bitPtr); var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) { if (n >= numBitsLeftInThisByte) {
var mask = (BITMASK[numBitsLeftInThisByte] << bitPtr); var mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bytePtr++; bytePtr++;
bitPtr = 0; bitPtr = 0;
bitsIn += numBitsLeftInThisByte; bitsIn += numBitsLeftInThisByte;
n -= numBitsLeftInThisByte; n -= numBitsLeftInThisByte;
} }
else { else {
var mask = (BITMASK[n] << bitPtr); var mask = (BITMASK[n] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bitPtr += n; bitPtr += n;
bitsIn += n; bitsIn += n;
n = 0; n = 0;
} }
} }
if (movePointers) { if (movePointers) {
this.bitPtr = bitPtr; this.bitPtr = bitPtr;
this.bytePtr = bytePtr; this.bytePtr = bytePtr;
} }
return result; return result;
}; };
/** /**
* byte0 byte1 byte2 byte3 * byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0 * 7......0 | 7......0 | 7......0 | 7......0
* *
* The bit pointer starts at bit7 of byte0 and moves right until it reaches * The bit pointer starts at bit7 of byte0 and moves right until it reaches
* bit0 of byte0, then goes to bit7 of byte1, etc. * bit0 of byte0, then goes to bit7 of byte1, etc.
* @param {number} n The number of bits to peek. * @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false. * @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number. * @return {number} The peeked bits, as an unsigned number.
*/ */
bitjs.io.BitStream.prototype.peekBits_rtl = function(n, movePointers) { bitjs.io.BitStream.prototype.peekBits_rtl = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) { if (n <= 0 || typeof n != typeof 1) {
return 0; return 0;
} }
var movePointers = movePointers || false, var movePointers = movePointers || false,
bytePtr = this.bytePtr, bytePtr = this.bytePtr,
bitPtr = this.bitPtr, bitPtr = this.bitPtr,
result = 0, result = 0,
bytes = this.bytes; bytes = this.bytes;
// keep going until we have no more bits left to peek at // keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then // TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want. // shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time. // This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) { while (n > 0) {
if (bytePtr >= bytes.length) { if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr; bytes.length + ", bitPtr=" + bitPtr;
return -1; return -1;
} }
var numBitsLeftInThisByte = (8 - bitPtr); var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) { if (n >= numBitsLeftInThisByte) {
result <<= numBitsLeftInThisByte; result <<= numBitsLeftInThisByte;
result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]);
bytePtr++; bytePtr++;
bitPtr = 0; bitPtr = 0;
n -= numBitsLeftInThisByte; n -= numBitsLeftInThisByte;
} }
else { else {
result <<= n; result <<= n;
result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr)); result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr));
bitPtr += n; bitPtr += n;
n = 0; n = 0;
} }
} }
if (movePointers) { if (movePointers) {
this.bitPtr = bitPtr; this.bitPtr = bitPtr;
this.bytePtr = bytePtr; this.bytePtr = bytePtr;
} }
return result; return result;
}; };
/** /**
* Some voodoo magic. * Some voodoo magic.
*/ */
bitjs.io.BitStream.prototype.getBits = function() { bitjs.io.BitStream.prototype.getBits = function() {
return (((((this.bytes[this.bytePtr] & 0xff) << 16) + return (((((this.bytes[this.bytePtr] & 0xff) << 16) +
((this.bytes[this.bytePtr+1] & 0xff) << 8) + ((this.bytes[this.bytePtr+1] & 0xff) << 8) +
((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff); ((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff);
}; };
/** /**
* Reads n bits out of the stream, consuming them (moving the bit pointer). * Reads n bits out of the stream, consuming them (moving the bit pointer).
* @param {number} n The number of bits to read. * @param {number} n The number of bits to read.
* @return {number} The read bits, as an unsigned number. * @return {number} The read bits, as an unsigned number.
*/ */
bitjs.io.BitStream.prototype.readBits = function(n) { bitjs.io.BitStream.prototype.readBits = function(n) {
return this.peekBits(n, true); return this.peekBits(n, true);
}; };
/** /**
* This returns n bytes as a sub-array, advancing the pointer if movePointers * This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true. Only use this for uncompressed blocks as this throws away remaining * is true. Only use this for uncompressed blocks as this throws away remaining
* bits in the current byte. * bits in the current byte.
* @param {number} n The number of bytes to peek. * @param {number} n The number of bytes to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false. * @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {Uint8Array} The subarray. * @return {Uint8Array} The subarray.
*/ */
bitjs.io.BitStream.prototype.peekBytes = function(n, movePointers) { bitjs.io.BitStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) { if (n <= 0 || typeof n != typeof 1) {
return 0; return 0;
} }
// from http://tools.ietf.org/html/rfc1951#page-11 // from http://tools.ietf.org/html/rfc1951#page-11
// "Any bits of input up to the next byte boundary are ignored." // "Any bits of input up to the next byte boundary are ignored."
while (this.bitPtr != 0) { while (this.bitPtr != 0) {
this.readBits(1); this.readBits(1);
} }
var movePointers = movePointers || false; var movePointers = movePointers || false;
var bytePtr = this.bytePtr, var bytePtr = this.bytePtr,
bitPtr = this.bitPtr; bitPtr = this.bitPtr;
var result = this.bytes.subarray(bytePtr, bytePtr + n); var result = this.bytes.subarray(bytePtr, bytePtr + n);
if (movePointers) { if (movePointers) {
this.bytePtr += n; this.bytePtr += n;
} }
return result; return result;
}; };
/** /**
* @param {number} n The number of bytes to read. * @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray. * @return {Uint8Array} The subarray.
*/ */
bitjs.io.BitStream.prototype.readBytes = function(n) { bitjs.io.BitStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true); return this.peekBytes(n, true);
}; };
/** /**
* This object allows you to peek and consume bytes as numbers and strings * This object allows you to peek and consume bytes as numbers and strings
* out of an ArrayBuffer. In this buffer, everything must be byte-aligned. * out of an ArrayBuffer. In this buffer, everything must be byte-aligned.
* *
* @param {ArrayBuffer} ab The ArrayBuffer object. * @param {ArrayBuffer} ab The ArrayBuffer object.
* @param {number=} opt_offset The offset into the ArrayBuffer * @param {number=} opt_offset The offset into the ArrayBuffer
* @param {number=} opt_length The length of this BitStream * @param {number=} opt_length The length of this BitStream
* @constructor * @constructor
*/ */
bitjs.io.ByteStream = function(ab, opt_offset, opt_length) { bitjs.io.ByteStream = function(ab, opt_offset, opt_length) {
var offset = opt_offset || 0; var offset = opt_offset || 0;
var length = opt_length || ab.byteLength; var length = opt_length || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length); this.bytes = new Uint8Array(ab, offset, length);
this.ptr = 0; this.ptr = 0;
}; };
/** /**
* Peeks at the next n bytes as an unsigned number but does not advance the * Peeks at the next n bytes as an unsigned number but does not advance the
* pointer * pointer
* TODO: This apparently cannot read more than 4 bytes as a number? * TODO: This apparently cannot read more than 4 bytes as a number?
* @param {number} n The number of bytes to peek at. * @param {number} n The number of bytes to peek at.
* @return {number} The n bytes interpreted as an unsigned number. * @return {number} The n bytes interpreted as an unsigned number.
*/ */
bitjs.io.ByteStream.prototype.peekNumber = function(n) { bitjs.io.ByteStream.prototype.peekNumber = function(n) {
// TODO: return error if n would go past the end of the stream? // TODO: return error if n would go past the end of the stream?
if (n <= 0 || typeof n != typeof 1) if (n <= 0 || typeof n != typeof 1)
return -1; return -1;
var result = 0; var result = 0;
// read from last byte to first byte and roll them in // read from last byte to first byte and roll them in
var curByte = this.ptr + n - 1; var curByte = this.ptr + n - 1;
while (curByte >= this.ptr) { while (curByte >= this.ptr) {
result <<= 8; result <<= 8;
result |= this.bytes[curByte]; result |= this.bytes[curByte];
--curByte; --curByte;
} }
return result; return result;
}; };
/** /**
* Returns the next n bytes as an unsigned number (or -1 on error) * Returns the next n bytes as an unsigned number (or -1 on error)
* and advances the stream pointer n bytes. * and advances the stream pointer n bytes.
* @param {number} n The number of bytes to read. * @param {number} n The number of bytes to read.
* @return {number} The n bytes interpreted as an unsigned number. * @return {number} The n bytes interpreted as an unsigned number.
*/ */
bitjs.io.ByteStream.prototype.readNumber = function(n) { bitjs.io.ByteStream.prototype.readNumber = function(n) {
var num = this.peekNumber( n ); var num = this.peekNumber( n );
this.ptr += n; this.ptr += n;
return num; return num;
}; };
/** /**
* Returns the next n bytes as a signed number but does not advance the * Returns the next n bytes as a signed number but does not advance the
* pointer. * pointer.
* @param {number} n The number of bytes to read. * @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number. * @return {number} The bytes interpreted as a signed number.
*/ */
bitjs.io.ByteStream.prototype.peekSignedNumber = function(n) { bitjs.io.ByteStream.prototype.peekSignedNumber = function(n) {
var num = this.peekNumber(n); var num = this.peekNumber(n);
var HALF = Math.pow(2, (n * 8) - 1); var HALF = Math.pow(2, (n * 8) - 1);
var FULL = HALF * 2; var FULL = HALF * 2;
if (num >= HALF) num -= FULL; if (num >= HALF) num -= FULL;
return num; return num;
}; };
/** /**
* Returns the next n bytes as a signed number and advances the stream pointer. * Returns the next n bytes as a signed number and advances the stream pointer.
* @param {number} n The number of bytes to read. * @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number. * @return {number} The bytes interpreted as a signed number.
*/ */
bitjs.io.ByteStream.prototype.readSignedNumber = function(n) { bitjs.io.ByteStream.prototype.readSignedNumber = function(n) {
var num = this.peekSignedNumber(n); var num = this.peekSignedNumber(n);
this.ptr += n; this.ptr += n;
return num; return num;
}; };
/** /**
* This returns n bytes as a sub-array, advancing the pointer if movePointers * This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true. * is true.
* @param {number} n The number of bytes to read. * @param {number} n The number of bytes to read.
* @param {boolean} movePointers Whether to move the pointers. * @param {boolean} movePointers Whether to move the pointers.
* @return {Uint8Array} The subarray. * @return {Uint8Array} The subarray.
*/ */
bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) { bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) { if (n <= 0 || typeof n != typeof 1) {
return null; return null;
} }
var result = this.bytes.subarray(this.ptr, this.ptr + n); var result = this.bytes.subarray(this.ptr, this.ptr + n);
if (movePointers) { if (movePointers) {
this.ptr += n; this.ptr += n;
} }
return result; return result;
}; };
/** /**
* Reads the next n bytes as a sub-array. * Reads the next n bytes as a sub-array.
* @param {number} n The number of bytes to read. * @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray. * @return {Uint8Array} The subarray.
*/ */
bitjs.io.ByteStream.prototype.readBytes = function(n) { bitjs.io.ByteStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true); return this.peekBytes(n, true);
}; };
/** /**
* Peeks at the next n bytes as a string but does not advance the pointer. * Peeks at the next n bytes as a string but does not advance the pointer.
* @param {number} n The number of bytes to peek at. * @param {number} n The number of bytes to peek at.
* @return {string} The next n bytes as a string. * @return {string} The next n bytes as a string.
*/ */
bitjs.io.ByteStream.prototype.peekString = function(n) { bitjs.io.ByteStream.prototype.peekString = function(n) {
if (n <= 0 || typeof n != typeof 1) { if (n <= 0 || typeof n != typeof 1) {
return ""; return "";
} }
var result = ""; var result = "";
for (var p = this.ptr, end = this.ptr + n; p < end; ++p) { for (var p = this.ptr, end = this.ptr + n; p < end; ++p) {
result += String.fromCharCode(this.bytes[p]); result += String.fromCharCode(this.bytes[p]);
} }
return result; return result;
}; };
/** /**
* Returns the next n bytes as an ASCII string and advances the stream pointer * Returns the next n bytes as an ASCII string and advances the stream pointer
* n bytes. * n bytes.
* @param {number} n The number of bytes to read. * @param {number} n The number of bytes to read.
* @return {string} The next n bytes as a string. * @return {string} The next n bytes as a string.
*/ */
bitjs.io.ByteStream.prototype.readString = function(n) { bitjs.io.ByteStream.prototype.readString = function(n) {
var strToReturn = this.peekString(n); var strToReturn = this.peekString(n);
this.ptr += n; this.ptr += n;
return strToReturn; return strToReturn;
}; };
/** /**
* A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store.
* @param {number} numBytes The number of bytes to allocate. * @param {number} numBytes The number of bytes to allocate.
* @constructor * @constructor
*/ */
bitjs.io.ByteBuffer = function(numBytes) { bitjs.io.ByteBuffer = function(numBytes) {
if (typeof numBytes != typeof 1 || numBytes <= 0) { if (typeof numBytes != typeof 1 || numBytes <= 0) {
throw "Error! ByteBuffer initialized with '" + numBytes + "'"; throw "Error! ByteBuffer initialized with '" + numBytes + "'";
} }
this.data = new Uint8Array(numBytes); this.data = new Uint8Array(numBytes);
this.ptr = 0; this.ptr = 0;
}; };
/** /**
* @param {number} b The byte to insert. * @param {number} b The byte to insert.
*/ */
bitjs.io.ByteBuffer.prototype.insertByte = function(b) { bitjs.io.ByteBuffer.prototype.insertByte = function(b) {
// TODO: throw if byte is invalid? // TODO: throw if byte is invalid?
this.data[this.ptr++] = b; this.data[this.ptr++] = b;
}; };
/** /**
* @param {Array.<number>|Uint8Array|Int8Array} bytes The bytes to insert. * @param {Array.<number>|Uint8Array|Int8Array} bytes The bytes to insert.
*/ */
bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) { bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) {
// TODO: throw if bytes is invalid? // TODO: throw if bytes is invalid?
this.data.set(bytes, this.ptr); this.data.set(bytes, this.ptr);
this.ptr += bytes.length; this.ptr += bytes.length;
}; };
/** /**
* Writes an unsigned number into the next n bytes. If the number is too large * Writes an unsigned number into the next n bytes. If the number is too large
* to fit into n bytes or is negative, an error is thrown. * to fit into n bytes or is negative, an error is thrown.
* @param {number} num The unsigned number to write. * @param {number} num The unsigned number to write.
* @param {number} numBytes The number of bytes to write the number into. * @param {number} numBytes The number of bytes to write the number into.
*/ */
bitjs.io.ByteBuffer.prototype.writeNumber = function(num, numBytes) { bitjs.io.ByteBuffer.prototype.writeNumber = function(num, numBytes) {
if (numBytes < 1) { if (numBytes < 1) {
throw 'Trying to write into too few bytes: ' + numBytes; throw 'Trying to write into too few bytes: ' + numBytes;
} }
if (num < 0) { if (num < 0) {
throw 'Trying to write a negative number (' + num + throw 'Trying to write a negative number (' + num +
') as an unsigned number to an ArrayBuffer'; ') as an unsigned number to an ArrayBuffer';
} }
if (num > (Math.pow(2, numBytes * 8) - 1)) { if (num > (Math.pow(2, numBytes * 8) - 1)) {
throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes';
} }
// Roll 8-bits at a time into an array of bytes. // Roll 8-bits at a time into an array of bytes.
var bytes = []; var bytes = [];
while (numBytes-- > 0) { while (numBytes-- > 0) {
var eightBits = num & 255; var eightBits = num & 255;
bytes.push(eightBits); bytes.push(eightBits);
num >>= 8; num >>= 8;
} }
this.insertBytes(bytes); this.insertBytes(bytes);
}; };
/** /**
* Writes a signed number into the next n bytes. If the number is too large * Writes a signed number into the next n bytes. If the number is too large
* to fit into n bytes, an error is thrown. * to fit into n bytes, an error is thrown.
* @param {number} num The signed number to write. * @param {number} num The signed number to write.
* @param {number} numBytes The number of bytes to write the number into. * @param {number} numBytes The number of bytes to write the number into.
*/ */
bitjs.io.ByteBuffer.prototype.writeSignedNumber = function(num, numBytes) { bitjs.io.ByteBuffer.prototype.writeSignedNumber = function(num, numBytes) {
if (numBytes < 1) { if (numBytes < 1) {
throw 'Trying to write into too few bytes: ' + numBytes; throw 'Trying to write into too few bytes: ' + numBytes;
} }
var HALF = Math.pow(2, (numBytes * 8) - 1); var HALF = Math.pow(2, (numBytes * 8) - 1);
if (num >= HALF || num < -HALF) { if (num >= HALF || num < -HALF) {
throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes';
} }
// Roll 8-bits at a time into an array of bytes. // Roll 8-bits at a time into an array of bytes.
var bytes = []; var bytes = [];
while (numBytes-- > 0) { while (numBytes-- > 0) {
var eightBits = num & 255; var eightBits = num & 255;
bytes.push(eightBits); bytes.push(eightBits);
num >>= 8; num >>= 8;
} }
this.insertBytes(bytes); this.insertBytes(bytes);
}; };
/** /**
* @param {string} str The ASCII string to write. * @param {string} str The ASCII string to write.
*/ */
bitjs.io.ByteBuffer.prototype.writeASCIIString = function(str) { bitjs.io.ByteBuffer.prototype.writeASCIIString = function(str) {
for (var i = 0; i < str.length; ++i) { for (var i = 0; i < str.length; ++i) {
var curByte = str.charCodeAt(i); var curByte = str.charCodeAt(i);
if (curByte < 0 || curByte > 255) { if (curByte < 0 || curByte > 255) {
throw 'Trying to write a non-ASCII string!'; throw 'Trying to write a non-ASCII string!';
} }
this.insertByte(curByte); this.insertByte(curByte);
} }
}; };
})(); })();
...@@ -24,19 +24,19 @@ var totalFilesInArchive = 0; ...@@ -24,19 +24,19 @@ var totalFilesInArchive = 0;
// Helper functions. // Helper functions.
var info = function(str) { var info = function(str) {
postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
}; };
var err = function(str) { var err = function(str) {
postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
}; };
var postProgress = function() { var postProgress = function() {
postMessage(new bitjs.archive.UnarchiveProgressEvent( postMessage(new bitjs.archive.UnarchiveProgressEvent(
currentFilename, currentFilename,
currentFileNumber, currentFileNumber,
currentBytesUnarchivedInFile, currentBytesUnarchivedInFile,
currentBytesUnarchived, currentBytesUnarchived,
totalUncompressedBytesInArchive, totalUncompressedBytesInArchive,
totalFilesInArchive)); totalFilesInArchive));
}; };
var zLocalFileHeaderSignature = 0x04034b50; var zLocalFileHeaderSignature = 0x04034b50;
...@@ -48,82 +48,82 @@ var zEndOfCentralDirLocatorSignature = 0x07064b50; ...@@ -48,82 +48,82 @@ var zEndOfCentralDirLocatorSignature = 0x07064b50;
// takes a ByteStream and parses out the local file information // takes a ByteStream and parses out the local file information
var ZipLocalFile = function(bstream) { var ZipLocalFile = function(bstream) {
if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function(){}) { if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function(){}) {
return null; return null;
} }
bstream.readNumber(4); // swallow signature bstream.readNumber(4); // swallow signature
this.version = bstream.readNumber(2); this.version = bstream.readNumber(2);
this.generalPurpose = bstream.readNumber(2); this.generalPurpose = bstream.readNumber(2);
this.compressionMethod = bstream.readNumber(2); this.compressionMethod = bstream.readNumber(2);
this.lastModFileTime = bstream.readNumber(2); this.lastModFileTime = bstream.readNumber(2);
this.lastModFileDate = bstream.readNumber(2); this.lastModFileDate = bstream.readNumber(2);
this.crc32 = bstream.readNumber(4); this.crc32 = bstream.readNumber(4);
this.compressedSize = bstream.readNumber(4); this.compressedSize = bstream.readNumber(4);
this.uncompressedSize = bstream.readNumber(4); this.uncompressedSize = bstream.readNumber(4);
this.fileNameLength = bstream.readNumber(2); this.fileNameLength = bstream.readNumber(2);
this.extraFieldLength = bstream.readNumber(2); this.extraFieldLength = bstream.readNumber(2);
this.filename = null; this.filename = null;
if (this.fileNameLength > 0) { if (this.fileNameLength > 0) {
this.filename = bstream.readString(this.fileNameLength); this.filename = bstream.readString(this.fileNameLength);
} }
info("Zip Local File Header:"); info("Zip Local File Header:");
info(" version=" + this.version); info(" version=" + this.version);
info(" general purpose=" + this.generalPurpose); info(" general purpose=" + this.generalPurpose);
info(" compression method=" + this.compressionMethod); info(" compression method=" + this.compressionMethod);
info(" last mod file time=" + this.lastModFileTime); info(" last mod file time=" + this.lastModFileTime);
info(" last mod file date=" + this.lastModFileDate); info(" last mod file date=" + this.lastModFileDate);
info(" crc32=" + this.crc32); info(" crc32=" + this.crc32);
info(" compressed size=" + this.compressedSize); info(" compressed size=" + this.compressedSize);
info(" uncompressed size=" + this.uncompressedSize); info(" uncompressed size=" + this.uncompressedSize);
info(" file name length=" + this.fileNameLength); info(" file name length=" + this.fileNameLength);
info(" extra field length=" + this.extraFieldLength); info(" extra field length=" + this.extraFieldLength);
info(" filename = '" + this.filename + "'"); info(" filename = '" + this.filename + "'");
this.extraField = null; this.extraField = null;
if (this.extraFieldLength > 0) { if (this.extraFieldLength > 0) {
this.extraField = bstream.readString(this.extraFieldLength); this.extraField = bstream.readString(this.extraFieldLength);
info(" extra field=" + this.extraField); info(" extra field=" + this.extraField);
} }
// read in the compressed data // read in the compressed data
this.fileData = null; this.fileData = null;
if (this.compressedSize > 0) { if (this.compressedSize > 0) {
this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.compressedSize); this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.compressedSize);
bstream.ptr += this.compressedSize; bstream.ptr += this.compressedSize;
} }
// TODO: deal with data descriptor if present (we currently assume no data descriptor!) // TODO: deal with data descriptor if present (we currently assume no data descriptor!)
// "This descriptor exists only if bit 3 of the general purpose bit flag is set" // "This descriptor exists only if bit 3 of the general purpose bit flag is set"
// But how do you figure out how big the file data is if you don't know the compressedSize // But how do you figure out how big the file data is if you don't know the compressedSize
// from the header?!? // from the header?!?
if ((this.generalPurpose & bitjs.BIT[3]) != 0) { if ((this.generalPurpose & bitjs.BIT[3]) != 0) {
this.crc32 = bstream.readNumber(4); this.crc32 = bstream.readNumber(4);
this.compressedSize = bstream.readNumber(4); this.compressedSize = bstream.readNumber(4);
this.uncompressedSize = bstream.readNumber(4); this.uncompressedSize = bstream.readNumber(4);
} }
}; };
// determine what kind of compressed data we have and decompress // determine what kind of compressed data we have and decompress
ZipLocalFile.prototype.unzip = function() { ZipLocalFile.prototype.unzip = function() {
// Zip Version 1.0, no compression (store only) // Zip Version 1.0, no compression (store only)
if (this.compressionMethod == 0 ) { if (this.compressionMethod == 0 ) {
info("ZIP v"+this.version+", store only: " + this.filename + " (" + this.compressedSize + " bytes)"); info("ZIP v"+this.version+", store only: " + this.filename + " (" + this.compressedSize + " bytes)");
currentBytesUnarchivedInFile = this.compressedSize; currentBytesUnarchivedInFile = this.compressedSize;
currentBytesUnarchived += this.compressedSize; currentBytesUnarchived += this.compressedSize;
} }
// version == 20, compression method == 8 (DEFLATE) // version == 20, compression method == 8 (DEFLATE)
else if (this.compressionMethod == 8) { else if (this.compressionMethod == 8) {
info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)"); info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)");
this.fileData = inflate(this.fileData, this.uncompressedSize); this.fileData = inflate(this.fileData, this.uncompressedSize);
} }
else { else {
err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)"); err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)");
this.fileData = null; this.fileData = null;
} }
}; };
...@@ -131,112 +131,112 @@ ZipLocalFile.prototype.unzip = function() { ...@@ -131,112 +131,112 @@ ZipLocalFile.prototype.unzip = function() {
// returns null on error // returns null on error
// returns an array of DecompressedFile objects on success // returns an array of DecompressedFile objects on success
var unzip = function(arrayBuffer) { var unzip = function(arrayBuffer) {
postMessage(new bitjs.archive.UnarchiveStartEvent()); postMessage(new bitjs.archive.UnarchiveStartEvent());
currentFilename = ""; currentFilename = "";
currentFileNumber = 0; currentFileNumber = 0;
currentBytesUnarchivedInFile = 0; currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0; currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0; totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0; totalFilesInArchive = 0;
currentBytesUnarchived = 0; currentBytesUnarchived = 0;
var bstream = new bitjs.io.ByteStream(arrayBuffer); var bstream = new bitjs.io.ByteStream(arrayBuffer);
// detect local file header signature or return null // detect local file header signature or return null
if (bstream.peekNumber(4) == zLocalFileHeaderSignature) { if (bstream.peekNumber(4) == zLocalFileHeaderSignature) {
var localFiles = []; var localFiles = [];
// loop until we don't see any more local files // loop until we don't see any more local files
while (bstream.peekNumber(4) == zLocalFileHeaderSignature) { while (bstream.peekNumber(4) == zLocalFileHeaderSignature) {
var oneLocalFile = new ZipLocalFile(bstream); var oneLocalFile = new ZipLocalFile(bstream);
// this should strip out directories/folders // this should strip out directories/folders
if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) { if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) {
localFiles.push(oneLocalFile); localFiles.push(oneLocalFile);
totalUncompressedBytesInArchive += oneLocalFile.uncompressedSize; totalUncompressedBytesInArchive += oneLocalFile.uncompressedSize;
} }
} }
totalFilesInArchive = localFiles.length; totalFilesInArchive = localFiles.length;
// got all local files, now sort them // got all local files, now sort them
localFiles.sort(function(a,b) { localFiles.sort(function(a,b) {
var aname = a.filename.toLowerCase(); var aname = a.filename.toLowerCase();
var bname = b.filename.toLowerCase(); var bname = b.filename.toLowerCase();
return aname > bname ? 1 : -1; return aname > bname ? 1 : -1;
}); });
// archive extra data record // archive extra data record
if (bstream.peekNumber(4) == zArchiveExtraDataSignature) { if (bstream.peekNumber(4) == zArchiveExtraDataSignature) {
info(" Found an Archive Extra Data Signature"); info(" Found an Archive Extra Data Signature");
// skipping this record for now // skipping this record for now
bstream.readNumber(4); bstream.readNumber(4);
var archiveExtraFieldLength = bstream.readNumber(4); var archiveExtraFieldLength = bstream.readNumber(4);
bstream.readString(archiveExtraFieldLength); bstream.readString(archiveExtraFieldLength);
} }
// central directory structure // central directory structure
// TODO: handle the rest of the structures (Zip64 stuff) // TODO: handle the rest of the structures (Zip64 stuff)
if (bstream.peekNumber(4) == zCentralFileHeaderSignature) { if (bstream.peekNumber(4) == zCentralFileHeaderSignature) {
info(" Found a Central File Header"); info(" Found a Central File Header");
// read all file headers // read all file headers
while (bstream.peekNumber(4) == zCentralFileHeaderSignature) { while (bstream.peekNumber(4) == zCentralFileHeaderSignature) {
bstream.readNumber(4); // signature bstream.readNumber(4); // signature
bstream.readNumber(2); // version made by bstream.readNumber(2); // version made by
bstream.readNumber(2); // version needed to extract bstream.readNumber(2); // version needed to extract
bstream.readNumber(2); // general purpose bit flag bstream.readNumber(2); // general purpose bit flag
bstream.readNumber(2); // compression method bstream.readNumber(2); // compression method
bstream.readNumber(2); // last mod file time bstream.readNumber(2); // last mod file time
bstream.readNumber(2); // last mod file date bstream.readNumber(2); // last mod file date
bstream.readNumber(4); // crc32 bstream.readNumber(4); // crc32
bstream.readNumber(4); // compressed size bstream.readNumber(4); // compressed size
bstream.readNumber(4); // uncompressed size bstream.readNumber(4); // uncompressed size
var fileNameLength = bstream.readNumber(2); // file name length var fileNameLength = bstream.readNumber(2); // file name length
var extraFieldLength = bstream.readNumber(2); // extra field length var extraFieldLength = bstream.readNumber(2); // extra field length
var fileCommentLength = bstream.readNumber(2); // file comment length var fileCommentLength = bstream.readNumber(2); // file comment length
bstream.readNumber(2); // disk number start bstream.readNumber(2); // disk number start
bstream.readNumber(2); // internal file attributes bstream.readNumber(2); // internal file attributes
bstream.readNumber(4); // external file attributes bstream.readNumber(4); // external file attributes
bstream.readNumber(4); // relative offset of local header bstream.readNumber(4); // relative offset of local header
bstream.readString(fileNameLength); // file name bstream.readString(fileNameLength); // file name
bstream.readString(extraFieldLength); // extra field bstream.readString(extraFieldLength); // extra field
bstream.readString(fileCommentLength); // file comment bstream.readString(fileCommentLength); // file comment
} }
} }
// digital signature // digital signature
if (bstream.peekNumber(4) == zDigitalSignatureSignature) { if (bstream.peekNumber(4) == zDigitalSignatureSignature) {
info(" Found a Digital Signature"); info(" Found a Digital Signature");
bstream.readNumber(4); bstream.readNumber(4);
var sizeOfSignature = bstream.readNumber(2); var sizeOfSignature = bstream.readNumber(2);
bstream.readString(sizeOfSignature); // digital signature data bstream.readString(sizeOfSignature); // digital signature data
} }
// report # files and total length // report # files and total length
if (localFiles.length > 0) { if (localFiles.length > 0) {
postProgress(); postProgress();
} }
// now do the unzipping of each file // now do the unzipping of each file
for (var i = 0; i < localFiles.length; ++i) { for (var i = 0; i < localFiles.length; ++i) {
var localfile = localFiles[i]; var localfile = localFiles[i];
// update progress // update progress
currentFilename = localfile.filename; currentFilename = localfile.filename;
currentFileNumber = i; currentFileNumber = i;
currentBytesUnarchivedInFile = 0; currentBytesUnarchivedInFile = 0;
// actually do the unzipping // actually do the unzipping
localfile.unzip(); localfile.unzip();
if (localfile.fileData != null) { if (localfile.fileData != null) {
postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile)); postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
postProgress(); postProgress();
} }
} }
postProgress(); postProgress();
postMessage(new bitjs.archive.UnarchiveFinishEvent()); postMessage(new bitjs.archive.UnarchiveFinishEvent());
} }
} }
...@@ -244,305 +244,305 @@ var unzip = function(arrayBuffer) { ...@@ -244,305 +244,305 @@ var unzip = function(arrayBuffer) {
// each entry's index is its code and its value is a JavaScript object // each entry's index is its code and its value is a JavaScript object
// containing {length: 6, symbol: X} // containing {length: 6, symbol: X}
function getHuffmanCodes(bitLengths) { function getHuffmanCodes(bitLengths) {
// ensure bitLengths is an array containing at least one element // ensure bitLengths is an array containing at least one element
if (typeof bitLengths != typeof [] || bitLengths.length < 1) { if (typeof bitLengths != typeof [] || bitLengths.length < 1) {
err("Error! getHuffmanCodes() called with an invalid array"); err("Error! getHuffmanCodes() called with an invalid array");
return null; return null;
} }
// Reference: http://tools.ietf.org/html/rfc1951#page-8 // Reference: http://tools.ietf.org/html/rfc1951#page-8
var numLengths = bitLengths.length, var numLengths = bitLengths.length,
bl_count = [], bl_count = [],
MAX_BITS = 1; MAX_BITS = 1;
// Step 1: count up how many codes of each length we have // Step 1: count up how many codes of each length we have
for (var i = 0; i < numLengths; ++i) { for (var i = 0; i < numLengths; ++i) {
var length = bitLengths[i]; var length = bitLengths[i];
// test to ensure each bit length is a positive, non-zero number // test to ensure each bit length is a positive, non-zero number
if (typeof length != typeof 1 || length < 0) { if (typeof length != typeof 1 || length < 0) {
err("bitLengths contained an invalid number in getHuffmanCodes(): " + length + " of type " + (typeof length)); err("bitLengths contained an invalid number in getHuffmanCodes(): " + length + " of type " + (typeof length));
return null; return null;
} }
// increment the appropriate bitlength count // increment the appropriate bitlength count
if (bl_count[length] == undefined) bl_count[length] = 0; if (bl_count[length] == undefined) bl_count[length] = 0;
// a length of zero means this symbol is not participating in the huffman coding // a length of zero means this symbol is not participating in the huffman coding
if (length > 0) bl_count[length]++; if (length > 0) bl_count[length]++;
if (length > MAX_BITS) MAX_BITS = length; if (length > MAX_BITS) MAX_BITS = length;
} }
// Step 2: Find the numerical value of the smallest code for each code length // Step 2: Find the numerical value of the smallest code for each code length
var next_code = [], var next_code = [],
code = 0; code = 0;
for (var bits = 1; bits <= MAX_BITS; ++bits) { for (var bits = 1; bits <= MAX_BITS; ++bits) {
var length = bits-1; var length = bits-1;
// ensure undefined lengths are zero // ensure undefined lengths are zero
if (bl_count[length] == undefined) bl_count[length] = 0; if (bl_count[length] == undefined) bl_count[length] = 0;
code = (code + bl_count[bits-1]) << 1; code = (code + bl_count[bits-1]) << 1;
next_code[bits] = code; next_code[bits] = code;
} }
// Step 3: Assign numerical values to all codes // Step 3: Assign numerical values to all codes
var table = {}, tableLength = 0; var table = {}, tableLength = 0;
for (var n = 0; n < numLengths; ++n) { for (var n = 0; n < numLengths; ++n) {
var len = bitLengths[n]; var len = bitLengths[n];
if (len != 0) { if (len != 0) {
table[next_code[len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(next_code[len],len) }; table[next_code[len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(next_code[len],len) };
tableLength++; tableLength++;
next_code[len]++; next_code[len]++;
} }
} }
table.maxLength = tableLength; table.maxLength = tableLength;
return table; return table;
} }
/* /*
The Huffman codes for the two alphabets are fixed, and are not The Huffman codes for the two alphabets are fixed, and are not
represented explicitly in the data. The Huffman code lengths represented explicitly in the data. The Huffman code lengths
for the literal/length alphabet are: for the literal/length alphabet are:
Lit Value Bits Codes Lit Value Bits Codes
--------- ---- ----- --------- ---- -----
0 - 143 8 00110000 through 0 - 143 8 00110000 through
10111111 10111111
144 - 255 9 110010000 through 144 - 255 9 110010000 through
111111111 111111111
256 - 279 7 0000000 through 256 - 279 7 0000000 through
0010111 0010111
280 - 287 8 11000000 through 280 - 287 8 11000000 through
11000111 11000111
*/ */
// fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits // fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits
var fixedHCtoLiteral = null; var fixedHCtoLiteral = null;
var fixedHCtoDistance = null; var fixedHCtoDistance = null;
function getFixedLiteralTable() { function getFixedLiteralTable() {
// create once // create once
if (!fixedHCtoLiteral) { if (!fixedHCtoLiteral) {
var bitlengths = new Array(288); var bitlengths = new Array(288);
for (var i = 0; i <= 143; ++i) bitlengths[i] = 8; for (var i = 0; i <= 143; ++i) bitlengths[i] = 8;
for (i = 144; i <= 255; ++i) bitlengths[i] = 9; for (i = 144; i <= 255; ++i) bitlengths[i] = 9;
for (i = 256; i <= 279; ++i) bitlengths[i] = 7; for (i = 256; i <= 279; ++i) bitlengths[i] = 7;
for (i = 280; i <= 287; ++i) bitlengths[i] = 8; for (i = 280; i <= 287; ++i) bitlengths[i] = 8;
// get huffman code table // get huffman code table
fixedHCtoLiteral = getHuffmanCodes(bitlengths); fixedHCtoLiteral = getHuffmanCodes(bitlengths);
} }
return fixedHCtoLiteral; return fixedHCtoLiteral;
} }
function getFixedDistanceTable() { function getFixedDistanceTable() {
// create once // create once
if (!fixedHCtoDistance) { if (!fixedHCtoDistance) {
var bitlengths = new Array(32); var bitlengths = new Array(32);
for (var i = 0; i < 32; ++i) { bitlengths[i] = 5; } for (var i = 0; i < 32; ++i) { bitlengths[i] = 5; }
// get huffman code table // get huffman code table
fixedHCtoDistance = getHuffmanCodes(bitlengths); fixedHCtoDistance = getHuffmanCodes(bitlengths);
} }
return fixedHCtoDistance; return fixedHCtoDistance;
} }
// extract one bit at a time until we find a matching Huffman Code // extract one bit at a time until we find a matching Huffman Code
// then return that symbol // then return that symbol
function decodeSymbol(bstream, hcTable) { function decodeSymbol(bstream, hcTable) {
var code = 0, len = 0; var code = 0, len = 0;
var match = false; var match = false;
// loop until we match // loop until we match
for (;;) { for (;;) {
// read in next bit // read in next bit
var bit = bstream.readBits(1); var bit = bstream.readBits(1);
code = (code<<1) | bit; code = (code<<1) | bit;
++len; ++len;
// check against Huffman Code table and break if found // check against Huffman Code table and break if found
if (hcTable.hasOwnProperty(code) && hcTable[code].length == len) { if (hcTable.hasOwnProperty(code) && hcTable[code].length == len) {
break; break;
} }
if (len > hcTable.maxLength) { if (len > hcTable.maxLength) {
err("Bit stream out of sync, didn't find a Huffman Code, length was " + len + err("Bit stream out of sync, didn't find a Huffman Code, length was " + len +
" and table only max code length of " + hcTable.maxLength); " and table only max code length of " + hcTable.maxLength);
break; break;
} }
} }
return hcTable[code].symbol; return hcTable[code].symbol;
} }
var CodeLengthCodeOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; var CodeLengthCodeOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
/* /*
Extra Extra Extra Extra Extra Extra
Code Bits Length(s) Code Bits Lengths Code Bits Length(s) Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
---- ---- ------ ---- ---- ------- ---- ---- ------- ---- ---- ------ ---- ---- ------- ---- ---- -------
257 0 3 267 1 15,16 277 4 67-82 257 0 3 267 1 15,16 277 4 67-82
258 0 4 268 1 17,18 278 4 83-98 258 0 4 268 1 17,18 278 4 83-98
259 0 5 269 2 19-22 279 4 99-114 259 0 5 269 2 19-22 279 4 99-114
260 0 6 270 2 23-26 280 4 115-130 260 0 6 270 2 23-26 280 4 115-130
261 0 7 271 2 27-30 281 5 131-162 261 0 7 271 2 27-30 281 5 131-162
262 0 8 272 2 31-34 282 5 163-194 262 0 8 272 2 31-34 282 5 163-194
263 0 9 273 3 35-42 283 5 195-226 263 0 9 273 3 35-42 283 5 195-226
264 0 10 274 3 43-50 284 5 227-257 264 0 10 274 3 43-50 284 5 227-257
265 1 11,12 275 3 51-58 285 0 258 265 1 11,12 275 3 51-58 285 0 258
266 1 13,14 276 3 59-66 266 1 13,14 276 3 59-66
*/ */
var LengthLookupTable = [ var LengthLookupTable = [
[0,3], [0,4], [0,5], [0,6], [0,3], [0,4], [0,5], [0,6],
[0,7], [0,8], [0,9], [0,10], [0,7], [0,8], [0,9], [0,10],
[1,11], [1,13], [1,15], [1,17], [1,11], [1,13], [1,15], [1,17],
[2,19], [2,23], [2,27], [2,31], [2,19], [2,23], [2,27], [2,31],
[3,35], [3,43], [3,51], [3,59], [3,35], [3,43], [3,51], [3,59],
[4,67], [4,83], [4,99], [4,115], [4,67], [4,83], [4,99], [4,115],
[5,131], [5,163], [5,195], [5,227], [5,131], [5,163], [5,195], [5,227],
[0,258] [0,258]
]; ];
/* /*
Extra Extra Extra Extra Extra Extra
Code Bits Dist Code Bits Dist Code Bits Distance Code Bits Dist Code Bits Dist Code Bits Distance
---- ---- ---- ---- ---- ------ ---- ---- -------- ---- ---- ---- ---- ---- ------ ---- ---- --------
0 0 1 10 4 33-48 20 9 1025-1536 0 0 1 10 4 33-48 20 9 1025-1536
1 0 2 11 4 49-64 21 9 1537-2048 1 0 2 11 4 49-64 21 9 1537-2048
2 0 3 12 5 65-96 22 10 2049-3072 2 0 3 12 5 65-96 22 10 2049-3072
3 0 4 13 5 97-128 23 10 3073-4096 3 0 4 13 5 97-128 23 10 3073-4096
4 1 5,6 14 6 129-192 24 11 4097-6144 4 1 5,6 14 6 129-192 24 11 4097-6144
5 1 7,8 15 6 193-256 25 11 6145-8192 5 1 7,8 15 6 193-256 25 11 6145-8192
6 2 9-12 16 7 257-384 26 12 8193-12288 6 2 9-12 16 7 257-384 26 12 8193-12288
7 2 13-16 17 7 385-512 27 12 12289-16384 7 2 13-16 17 7 385-512 27 12 12289-16384
8 3 17-24 18 8 513-768 28 13 16385-24576 8 3 17-24 18 8 513-768 28 13 16385-24576
9 3 25-32 19 8 769-1024 29 13 24577-32768 9 3 25-32 19 8 769-1024 29 13 24577-32768
*/ */
var DistLookupTable = [ var DistLookupTable = [
[0,1], [0,2], [0,3], [0,4], [0,1], [0,2], [0,3], [0,4],
[1,5], [1,7], [1,5], [1,7],
[2,9], [2,13], [2,9], [2,13],
[3,17], [3,25], [3,17], [3,25],
[4,33], [4,49], [4,33], [4,49],
[5,65], [5,97], [5,65], [5,97],
[6,129], [6,193], [6,129], [6,193],
[7,257], [7,385], [7,257], [7,385],
[8,513], [8,769], [8,513], [8,769],
[9,1025], [9,1537], [9,1025], [9,1537],
[10,2049], [10,3073], [10,2049], [10,3073],
[11,4097], [11,6145], [11,4097], [11,6145],
[12,8193], [12,12289], [12,8193], [12,12289],
[13,16385], [13,24577] [13,16385], [13,24577]
]; ];
function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
/* /*
loop (until end of block code recognized) loop (until end of block code recognized)
decode literal/length value from input stream decode literal/length value from input stream
if value < 256 if value < 256
copy value (literal byte) to output stream copy value (literal byte) to output stream
otherwise otherwise
if value = end of block (256) if value = end of block (256)
break from loop break from loop
otherwise (value = 257..285) otherwise (value = 257..285)
decode distance from input stream decode distance from input stream
move backwards distance bytes in the output move backwards distance bytes in the output
stream, and copy length bytes from this stream, and copy length bytes from this
position to the output stream. position to the output stream.
*/ */
var numSymbols = 0, blockSize = 0; var numSymbols = 0, blockSize = 0;
for (;;) { for (;;) {
var symbol = decodeSymbol(bstream, hcLiteralTable); var symbol = decodeSymbol(bstream, hcLiteralTable);
++numSymbols; ++numSymbols;
if (symbol < 256) { if (symbol < 256) {
// copy literal byte to output // copy literal byte to output
buffer.insertByte(symbol); buffer.insertByte(symbol);
blockSize++; blockSize++;
} }
else { else {
// end of block reached // end of block reached
if (symbol == 256) { if (symbol == 256) {
break; break;
} }
else { else {
var lengthLookup = LengthLookupTable[symbol-257], var lengthLookup = LengthLookupTable[symbol-257],
length = lengthLookup[1] + bstream.readBits(lengthLookup[0]), length = lengthLookup[1] + bstream.readBits(lengthLookup[0]),
distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)], distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)],
distance = distLookup[1] + bstream.readBits(distLookup[0]); distance = distLookup[1] + bstream.readBits(distLookup[0]);
// now apply length and distance appropriately and copy to output // now apply length and distance appropriately and copy to output
// TODO: check that backward distance < data.length? // TODO: check that backward distance < data.length?
// http://tools.ietf.org/html/rfc1951#page-11 // http://tools.ietf.org/html/rfc1951#page-11
// "Note also that the referenced string may overlap the current // "Note also that the referenced string may overlap the current
// position; for example, if the last 2 bytes decoded have values // position; for example, if the last 2 bytes decoded have values
// X and Y, a string reference with <length = 5, distance = 2> // X and Y, a string reference with <length = 5, distance = 2>
// adds X,Y,X,Y,X to the output stream." // adds X,Y,X,Y,X to the output stream."
// //
// loop for each character // loop for each character
var ch = buffer.ptr - distance; var ch = buffer.ptr - distance;
blockSize += length; blockSize += length;
if(length > distance) { if(length > distance) {
var data = buffer.data; var data = buffer.data;
while (length--) { while (length--) {
buffer.insertByte(data[ch++]); buffer.insertByte(data[ch++]);
} }
} else { } else {
buffer.insertBytes(buffer.data.subarray(ch, ch + length)) buffer.insertBytes(buffer.data.subarray(ch, ch + length))
} }
} // length-distance pair } // length-distance pair
} // length-distance pair or end-of-block } // length-distance pair or end-of-block
} // loop until we reach end of block } // loop until we reach end of block
return blockSize; return blockSize;
} }
// {Uint8Array} compressedData A Uint8Array of the compressed file data. // {Uint8Array} compressedData A Uint8Array of the compressed file data.
// compression method 8 // compression method 8
// deflate: http://tools.ietf.org/html/rfc1951 // deflate: http://tools.ietf.org/html/rfc1951
function inflate(compressedData, numDecompressedBytes) { function inflate(compressedData, numDecompressedBytes) {
// Bit stream representing the compressed data. // Bit stream representing the compressed data.
var bstream = new bitjs.io.BitStream(compressedData.buffer, var bstream = new bitjs.io.BitStream(compressedData.buffer,
false /* rtl */, false /* rtl */,
compressedData.byteOffset, compressedData.byteOffset,
compressedData.byteLength); compressedData.byteLength);
var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes); var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
var numBlocks = 0, blockSize = 0; var numBlocks = 0, blockSize = 0;
// block format: http://tools.ietf.org/html/rfc1951#page-9 // block format: http://tools.ietf.org/html/rfc1951#page-9
do { do {
var bFinal = bstream.readBits(1), var bFinal = bstream.readBits(1),
bType = bstream.readBits(2); bType = bstream.readBits(2);
blockSize = 0; blockSize = 0;
++numBlocks; ++numBlocks;
// no compression // no compression
if (bType == 0) { if (bType == 0) {
// skip remaining bits in this byte // skip remaining bits in this byte
while (bstream.bitPtr != 0) bstream.readBits(1); while (bstream.bitPtr != 0) bstream.readBits(1);
var len = bstream.readBits(16), var len = bstream.readBits(16),
nlen = bstream.readBits(16); nlen = bstream.readBits(16);
// TODO: check if nlen is the ones-complement of len? // TODO: check if nlen is the ones-complement of len?
if(len > 0) buffer.insertBytes(bstream.readBytes(len)); if(len > 0) buffer.insertBytes(bstream.readBytes(len));
blockSize = len; blockSize = len;
} }
// fixed Huffman codes // fixed Huffman codes
else if(bType == 1) { else if(bType == 1) {
blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer); blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer);
} }
// dynamic Huffman codes // dynamic Huffman codes
else if(bType == 2) { else if(bType == 2) {
var numLiteralLengthCodes = bstream.readBits(5) + 257; var numLiteralLengthCodes = bstream.readBits(5) + 257;
var numDistanceCodes = bstream.readBits(5) + 1, var numDistanceCodes = bstream.readBits(5) + 1,
numCodeLengthCodes = bstream.readBits(4) + 4; numCodeLengthCodes = bstream.readBits(4) + 4;
// populate the array of code length codes (first de-compaction) // populate the array of code length codes (first de-compaction)
var codeLengthsCodeLengths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; var codeLengthsCodeLengths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
for (var i = 0; i < numCodeLengthCodes; ++i) { for (var i = 0; i < numCodeLengthCodes; ++i) {
codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3); codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3);
} }
// get the Huffman Codes for the code lengths // get the Huffman Codes for the code lengths
var codeLengthsCodes = getHuffmanCodes(codeLengthsCodeLengths); var codeLengthsCodes = getHuffmanCodes(codeLengthsCodeLengths);
// now follow this mapping // now follow this mapping
/* /*
0 - 15: Represent code lengths of 0 - 15 0 - 15: Represent code lengths of 0 - 15
16: Copy the previous code length 3 - 6 times. 16: Copy the previous code length 3 - 6 times.
The next 2 bits indicate repeat length The next 2 bits indicate repeat length
...@@ -554,64 +554,64 @@ function inflate(compressedData, numDecompressedBytes) { ...@@ -554,64 +554,64 @@ function inflate(compressedData, numDecompressedBytes) {
(3 bits of length) (3 bits of length)
18: Repeat a code length of 0 for 11 - 138 times 18: Repeat a code length of 0 for 11 - 138 times
(7 bits of length) (7 bits of length)
*/ */
// to generate the true code lengths of the Huffman Codes for the literal // to generate the true code lengths of the Huffman Codes for the literal
// and distance tables together // and distance tables together
var literalCodeLengths = []; var literalCodeLengths = [];
var prevCodeLength = 0; var prevCodeLength = 0;
while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) { while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) {
var symbol = decodeSymbol(bstream, codeLengthsCodes); var symbol = decodeSymbol(bstream, codeLengthsCodes);
if (symbol <= 15) { if (symbol <= 15) {
literalCodeLengths.push(symbol); literalCodeLengths.push(symbol);
prevCodeLength = symbol; prevCodeLength = symbol;
} }
else if (symbol == 16) { else if (symbol == 16) {
var repeat = bstream.readBits(2) + 3; var repeat = bstream.readBits(2) + 3;
while (repeat--) { while (repeat--) {
literalCodeLengths.push(prevCodeLength); literalCodeLengths.push(prevCodeLength);
} }
} }
else if (symbol == 17) { else if (symbol == 17) {
var repeat = bstream.readBits(3) + 3; var repeat = bstream.readBits(3) + 3;
while (repeat--) { while (repeat--) {
literalCodeLengths.push(0); literalCodeLengths.push(0);
} }
} }
else if (symbol == 18) { else if (symbol == 18) {
var repeat = bstream.readBits(7) + 11; var repeat = bstream.readBits(7) + 11;
while (repeat--) { while (repeat--) {
literalCodeLengths.push(0); literalCodeLengths.push(0);
} }
} }
} }
// now split the distance code lengths out of the literal code array // now split the distance code lengths out of the literal code array
var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes); var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes);
// now generate the true Huffman Code tables using these code lengths // now generate the true Huffman Code tables using these code lengths
var hcLiteralTable = getHuffmanCodes(literalCodeLengths), var hcLiteralTable = getHuffmanCodes(literalCodeLengths),
hcDistanceTable = getHuffmanCodes(distanceCodeLengths); hcDistanceTable = getHuffmanCodes(distanceCodeLengths);
blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer); blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer);
} }
// error // error
else { else {
err("Error! Encountered deflate block of type 3"); err("Error! Encountered deflate block of type 3");
return null; return null;
} }
// update progress // update progress
currentBytesUnarchivedInFile += blockSize; currentBytesUnarchivedInFile += blockSize;
currentBytesUnarchived += blockSize; currentBytesUnarchived += blockSize;
postProgress(); postProgress();
} while (bFinal != 1); } while (bFinal != 1);
// we are done reading blocks if the bFinal bit was set for this block // we are done reading blocks if the bFinal bit was set for this block
// return the buffer data bytes // return the buffer data bytes
return buffer.data; return buffer.data;
} }
// event.data.file has the ArrayBuffer. // event.data.file has the ArrayBuffer.
onmessage = function(event) { onmessage = function(event) {
unzip(event.data.file, true); unzip(event.data.file, true);
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment