Added FLV extractor.

feature-extract-files
n1474335 2019-01-03 18:40:22 +00:00
parent cd0c86e0d6
commit 0449c46b38
2 changed files with 79 additions and 8 deletions

View File

@ -417,7 +417,7 @@ export const FILE_SIGNATURES = {
2: 0x56, 2: 0x56,
3: 0x1 3: 0x1
}, },
extractor: null extractor: extractFLV
}, },
], ],
"Audio": [ "Audio": [
@ -1285,3 +1285,49 @@ export function extractBMP(bytes, offset) {
return stream.carve(); return stream.carve();
} }
/**
* FLV extractor.
*
* @param {Uint8Array} bytes
* @param {number} offset
* @returns {Uint8Array}
*/
export function extractFLV(bytes, offset) {
const stream = new Stream(bytes.slice(offset));
// Move past signature, version and flags
stream.moveForwardsBy(5);
// Read header size
const headerSize = stream.readInt(4, "be");
// Skip through the rest of the header
stream.moveForwardsBy(headerSize - 9);
let tagSize = -11; // Fake size of previous tag header
while (stream.position < stream.length) {
const prevTagSize = stream.readInt(4, "be");
const tagType = stream.readInt(1, "be");
if ([8, 9, 18].indexOf(tagType) < 0) {
// This tag is not valid
stream.moveBackwardsBy(1);
break;
}
if (prevTagSize !== tagSize + 11) {
// Previous tag was not valid
stream.moveBackwardsBy(tagSize + 11);
break;
}
tagSize = stream.readInt(3, "be");
// Move past the rest of the tag header and payload
stream.moveForwardsBy(7 + tagSize);
}
return stream.carve();
}

View File

@ -22,6 +22,7 @@ export default class Stream {
constructor(input) { constructor(input) {
this.bytes = input; this.bytes = input;
this.position = 0; this.position = 0;
this.length = this.bytes.length;
} }
/** /**
@ -31,6 +32,8 @@ export default class Stream {
* @returns {Uint8Array} * @returns {Uint8Array}
*/ */
getBytes(numBytes) { getBytes(numBytes) {
if (this.position > this.length) return undefined;
const newPosition = this.position + numBytes; const newPosition = this.position + numBytes;
const bytes = this.bytes.slice(this.position, newPosition); const bytes = this.bytes.slice(this.position, newPosition);
this.position = newPosition; this.position = newPosition;
@ -45,6 +48,8 @@ export default class Stream {
* @returns {string} * @returns {string}
*/ */
readString(numBytes) { readString(numBytes) {
if (this.position > this.length) return undefined;
let result = ""; let result = "";
for (let i = this.position; i < this.position + numBytes; i++) { for (let i = this.position; i < this.position + numBytes; i++) {
const currentByte = this.bytes[i]; const currentByte = this.bytes[i];
@ -63,6 +68,8 @@ export default class Stream {
* @returns {number} * @returns {number}
*/ */
readInt(numBytes, endianness="be") { readInt(numBytes, endianness="be") {
if (this.position > this.length) return undefined;
let val = 0; let val = 0;
if (endianness === "be") { if (endianness === "be") {
for (let i = this.position; i < this.position + numBytes; i++) { for (let i = this.position; i < this.position + numBytes; i++) {
@ -85,8 +92,10 @@ export default class Stream {
* @param {number|List<number>} val * @param {number|List<number>} val
*/ */
continueUntil(val) { continueUntil(val) {
if (this.position > this.length) return;
if (typeof val === "number") { if (typeof val === "number") {
while (++this.position < this.bytes.length && this.bytes[this.position] !== val) { while (++this.position < this.length && this.bytes[this.position] !== val) {
continue; continue;
} }
return; return;
@ -94,13 +103,13 @@ export default class Stream {
// val is an array // val is an array
let found = false; let found = false;
while (!found && this.position < this.bytes.length) { while (!found && this.position < this.length) {
while (++this.position < this.bytes.length && this.bytes[this.position] !== val[0]) { while (++this.position < this.length && this.bytes[this.position] !== val[0]) {
continue; continue;
} }
found = true; found = true;
for (let i = 1; i < val.length; i++) { for (let i = 1; i < val.length; i++) {
if (this.position + i > this.bytes.length || this.bytes[this.position + i] !== val[i]) if (this.position + i > this.length || this.bytes[this.position + i] !== val[i])
found = false; found = false;
} }
} }
@ -122,7 +131,23 @@ export default class Stream {
* @param {number} numBytes * @param {number} numBytes
*/ */
moveForwardsBy(numBytes) { moveForwardsBy(numBytes) {
this.position += numBytes; const pos = this.position + numBytes;
if (pos < 0 || pos > this.length)
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
this.position = pos;
}
/**
* Move backwards through the stream by the specified number of bytes.
*
* @param {number} numBytes
*/
moveBackwardsBy(numBytes) {
const pos = this.position - numBytes;
if (pos < 0 || pos > this.length)
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
this.position = pos;
} }
/** /**
@ -131,7 +156,7 @@ export default class Stream {
* @param {number} pos * @param {number} pos
*/ */
moveTo(pos) { moveTo(pos) {
if (pos < 0 || pos > this.bytes.length - 1) if (pos < 0 || pos > this.length)
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds."); throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
this.position = pos; this.position = pos;
} }
@ -142,7 +167,7 @@ export default class Stream {
* @returns {boolean} * @returns {boolean}
*/ */
hasMore() { hasMore() {
return this.position < this.bytes.length; return this.position < this.length;
} }
/** /**