diff --git a/src/core/lib/FileSignatures.mjs b/src/core/lib/FileSignatures.mjs index 4409c96c..b0eb5884 100644 --- a/src/core/lib/FileSignatures.mjs +++ b/src/core/lib/FileSignatures.mjs @@ -650,7 +650,7 @@ export const FILE_SIGNATURES = { 56: 0x69, 57: 0x70 }, - extractor: null + extractor: extractZIP }, ], "Applications": [ @@ -790,7 +790,7 @@ export const FILE_SIGNATURES = { 1: 0x8b, 2: 0x8 }, - extractor: null + extractor: extractGZIP }, { name: "Bzip2", @@ -1309,7 +1309,7 @@ export function extractFLV(bytes, offset) { let tagSize = -11; // Fake size of previous tag header while (stream.hasMore()) { const prevTagSize = stream.readInt(4, "be"); - const tagType = stream.readInt(1, "be"); + const tagType = stream.readInt(1); if ([8, 9, 18].indexOf(tagType) < 0) { // This tag is not valid @@ -1346,14 +1346,14 @@ export function extractRTF(bytes, offset) { let openTags = 0; - if (stream.readInt(1, "be") !== 0x7b) { // { + if (stream.readInt(1) !== 0x7b) { // { throw new Error("Not a valid RTF file"); } else { openTags++; } while (openTags > 0 && stream.hasMore()) { - switch (stream.readInt(1, "be")) { + switch (stream.readInt(1)) { case 0x7b: // { openTags++; break; @@ -1372,3 +1372,102 @@ export function extractRTF(bytes, offset) { return stream.carve(); } + + +/** + * GZIP extractor. + * + * @param {Uint8Array} bytes + * @param {number} offset + * @returns {Uint8Array} + */ +export function extractGZIP(bytes, offset) { + const stream = new Stream(bytes.slice(offset)); + + /* HEADER */ + + // Skip over signature and compression method + stream.moveForwardsBy(3); + + // Read flags + const flags = stream.readInt(1); + + // Skip over last modification time + stream.moveForwardsBy(4); + + // Read compression flags + const compressionFlags = stream.readInt(1); + + // Skip over OS + stream.moveForwardsBy(1); + + + /* OPTIONAL HEADERS */ + + // Extra fields + if (flags & 0x4) { + console.log("Extra fields"); + const extraFieldsSize = stream.readInt(2, "le"); + stream.moveForwardsby(extraFieldsSize); + } + + // Original filename + if (flags & 0x8) { + console.log("Filename"); + stream.continueUntil(0x00); + stream.moveForwardsBy(1); + } + + // Comment + if (flags & 0x10) { + console.log("Comment"); + stream.continueUntil(0x00); + stream.moveForwardsBy(1); + } + + // Checksum + if (flags & 0x2) { + console.log("Checksum"); + stream.moveForwardsBy(2); + } + + + /* DEFLATE DATA */ + + let finalBlock = 0; + + while (!finalBlock) { + // Read header + const blockHeader = stream.readBits(3); + + finalBlock = blockHeader & 0x1; + const blockType = blockHeader & 0x6; + + if (blockType === 0) { + // No compression + stream.moveForwardsBy(1); + const blockLength = stream.readInt(2, "le"); + console.log("No compression. Length: " + blockLength); + stream.moveForwardsBy(2 + blockLength); + } else if (blockType === 1) { + // Fixed Huffman + + } else if (blockType === 2) { + // Dynamic Huffman + + } else { + throw new Error("Invalid block type"); + break; + } + } + + + /* FOOTER */ + + // Skip over checksum and size of original uncompressed input + stream.moveForwardsBy(8); + + console.log(stream.position); + + return stream.carve(); +} diff --git a/src/core/lib/Stream.mjs b/src/core/lib/Stream.mjs index 0a29b5a6..2b5d8d09 100644 --- a/src/core/lib/Stream.mjs +++ b/src/core/lib/Stream.mjs @@ -21,8 +21,9 @@ export default class Stream { */ constructor(input) { this.bytes = input; - this.position = 0; this.length = this.bytes.length; + this.position = 0; + this.bitPos = 0; } /** @@ -37,6 +38,7 @@ export default class Stream { const newPosition = this.position + numBytes; const bytes = this.bytes.slice(this.position, newPosition); this.position = newPosition; + this.bitPos = 0; return bytes; } @@ -57,6 +59,7 @@ export default class Stream { result += String.fromCharCode(currentByte); } this.position += numBytes; + this.bitPos = 0; return result; } @@ -83,9 +86,59 @@ export default class Stream { } } this.position += numBytes; + this.bitPos = 0; return val; } + + /** + * Reads a number of bits from the buffer. + * + * @TODO Add endianness + * + * @param {number} numBits + * @returns {number} + */ + readBits(numBits) { + if (this.position > this.length) return undefined; + + let bitBuf = 0, + bitBufLen = 0; + + // Add remaining bits from current byte + bitBuf = this.bytes[this.position++] & bitMask(this.bitPos); + bitBufLen = 8 - this.bitPos; + this.bitPos = 0; + + // Not enough bits yet + while (bitBufLen < numBits) { + bitBuf |= this.bytes[this.position++] << bitBufLen; + bitBufLen += 8; + } + + // Reverse back to numBits + if (bitBufLen > numBits) { + const excess = bitBufLen - numBits; + bitBuf >>>= excess; + bitBufLen -= excess; + this.position--; + this.bitPos = 8 - excess; + } + + return bitBuf; + + /** + * Calculates the bit mask based on the current bit position. + * + * @param {number} bitPos + * @returns {number} The bit mask + */ + function bitMask(bitPos) { + return (1 << (8 - bitPos)) - 1; + } + } + + /** * Consume the stream until we reach the specified byte or sequence of bytes. * @@ -94,6 +147,8 @@ export default class Stream { continueUntil(val) { if (this.position > this.length) return; + this.bitPos = 0; + if (typeof val === "number") { while (++this.position < this.length && this.bytes[this.position] !== val) { continue; @@ -121,8 +176,10 @@ export default class Stream { * @param {number} val */ consumeIf(val) { - if (this.bytes[this.position] === val) + if (this.bytes[this.position] === val) { this.position++; + this.bitPos = 0; + } } /** @@ -135,6 +192,7 @@ export default class Stream { if (pos < 0 || pos > this.length) throw new Error("Cannot move to position " + pos + " in stream. Out of bounds."); this.position = pos; + this.bitPos = 0; } @@ -148,6 +206,7 @@ export default class Stream { if (pos < 0 || pos > this.length) throw new Error("Cannot move to position " + pos + " in stream. Out of bounds."); this.position = pos; + this.bitPos = 0; } /** @@ -159,6 +218,7 @@ export default class Stream { if (pos < 0 || pos > this.length) throw new Error("Cannot move to position " + pos + " in stream. Out of bounds."); this.position = pos; + this.bitPos = 0; } /** @@ -176,6 +236,7 @@ export default class Stream { * @returns {Uint8Array} */ carve() { + if (this.bitPos > 0) this.position++; return this.bytes.slice(0, this.position); }