Skip bad buffers

This commit is contained in:
Bill 2021-08-12 02:48:27 -04:00
parent e681a316a1
commit 5603c4a010
4 changed files with 611 additions and 619 deletions

View File

@ -5,16 +5,16 @@
* MIT LICENCE
*
*/
import { fs } from 'fs';
import { lzma } from 'lzma-purejs';
import { Stream } from 'stream';
import { zlib } from 'zlib';
import { SWFBuffer } from './lib/swf-buffer';
import { SWFTags } from './lib/swf-tags';
const SWFReader = exports;
var fs = require('fs')
, zlib = require('zlib')
, lzma = require('lzma-purejs')
, Stream = require('stream')
, SWFBuffer = require('./lib/swf-buffer')
, SWFTags = require('./lib/swf-tags')
, SWFReader = exports;
function readSWFTags(buff, swf) {
function readSWFTags(buff, swf)
{
var tags = []
, tag
, tagHeader
@ -24,14 +24,16 @@ function readSWFTags(buff, swf) {
, fc;
/* Reads TagCodeAndLength from Tag's RECORDHEADER */
while( (tagHeader = buff.readTagCodeAndLength()) ) {
while( (tagHeader = buff.readTagCodeAndLength()) )
{
tag = {
header : tagHeader
};
switch( tagHeader.code ) {
case SWFTags.FileAttributes :
flag = buff.readUIntLE(32);
fileAttrs = {}
switch( tagHeader.code )
{
case SWFTags.FileAttributes: {
const flag = buff.readUIntLE(32);
const fileAttrs = {};
fileAttrs.useNetwork = tag.useNetwork = !!(flag & 0x1);
fileAttrs.as3 = tag.as3 = !!(flag & 0x8);
@ -41,13 +43,16 @@ function readSWFTags(buff, swf) {
swf.fileAttributes = fileAttrs;
break;
case SWFTags.Metadata :
swf.metadata = tag.metadata = buff.readString()
}
case SWFTags.Metadata: {
swf.metadata = tag.metadata = buff.readString();
break;
case SWFTags.SetBackgroundColor :
}
case SWFTags.SetBackgroundColor: {
tag.RGB = buff.readRGB();
swf.backgroundColor = '#' + (tag.RGB[0]*65536 + tag.RGB[1]*256 + tag.RGB[0]).toString(16);
break;
}
case SWFTags.Protect :
swf.protect = tagHeader.length && buff.readString();
break;
@ -101,7 +106,7 @@ function readSWFTags(buff, swf) {
// tag.shapes = buff.readShapeWithStyle();
// break;
case SWFTags.FrameLabel :
tag.name = buff.readString()
tag.name = buff.readString();
l = Buffer.byteLength(tag.name);
/* check if it's an named anchor */
if(l & (tagHeader.length - 1) != l)
@ -154,7 +159,8 @@ function readSWFTags(buff, swf) {
case SWFTags.ImportAssets2 :
tag.url = buff.readString();
if ( !(1 === buff.readUInt8() && 0 === buff.readUInt8()) ) {
if( !(1 === buff.readUInt8() && 0 === buff.readUInt8()) )
{
throw new Error('Reserved bits for ImportAssets2 used');
}
@ -170,13 +176,14 @@ function readSWFTags(buff, swf) {
});
break;
case SWFTags.EnableDebbuger :
tag.password = buff.readString()
tag.password = buff.readString();
break;
case SWFTags.EnableDebugger2 :
if (0 !== buff.readUIntLE(16)) {
if(0 !== buff.readUIntLE(16))
{
//throw new Error('Reserved bit for EnableDebugger2 used.');
}
tag.password = buff.readString()
tag.password = buff.readString();
break;
case SWFTags.ScriptLimits :
/**
@ -189,7 +196,7 @@ function readSWFTags(buff, swf) {
*/
tag.scriptTimeoutSeconds = buff.readUIntLE(16);
break;
case SWFTags.SymbolClass :
case SWFTags.SymbolClass: {
tag.numSymbols = buff.readUIntLE(16);
tag.symbols = [];
@ -201,29 +208,35 @@ function readSWFTags(buff, swf) {
name : buff.readString()
});
break;
case SWFTags.DefineScalingGrid :
}
case SWFTags.DefineScalingGrid: {
tag.characterId = buff.readUIntLE(16);
tag.splitter = buff.readRect();
break;
case SWFTags.setTabIndex :
}
case SWFTags.setTabIndex: {
tag.depth = buff.readUIntLE(16);
tag.tabIndex = buff.readUIntLE(16);
break;
case SWFTags.JPEGTables:
}
case SWFTags.JPEGTables: {
tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length);
buff.pointer += tagHeader.length;
break;
case SWFTags.DefineBits:
}
case SWFTags.DefineBits: {
tag.characterId = buff.readUIntLE(16);
tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2);
buff.pointer += tagHeader.length - 2;
break;
case SWFTags.DefineBitsJPEG2:
}
case SWFTags.DefineBitsJPEG2: {
tag.characterId = buff.readUIntLE(16);
tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2);
buff.pointer += tagHeader.length - 2;
break;
case SWFTags.DefineBitsJPEG3:
}
case SWFTags.DefineBitsJPEG3: {
tag.characterId = buff.readUIntLE(16);
var alphaDataOffset = buff.readUIntLE(32);
tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset);
@ -232,30 +245,34 @@ function readSWFTags(buff, swf) {
tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength);
buff.pointer += restLength;
break;
case SWFTags.DefineBitsJPEG4:
}
case SWFTags.DefineBitsJPEG4: {
tag.characterId = buff.readUIntLE(16);
var alphaDataOffset = buff.readUIntLE(32);
const alphaDataOffset = buff.readUIntLE(32);
tag.deblockParam = buff.readUIntLE(16);
tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset);
buff.pointer += alphaDataOffset;
var restLength = tagHeader.length - 8 - alphaDataOffset;
const restLength = tagHeader.length - 8 - alphaDataOffset;
tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength);
buff.pointer += restLength;
break;
}
case SWFTags.DefineBitsLossless:
case SWFTags.DefineBitsLossless2:
case SWFTags.DefineBitsLossless2: {
tag.characterId = buff.readUIntLE(16);
tag.bitmapFormat = buff.readUInt8();
tag.bitmapWidth = buff.readUIntLE(16);
tag.bitmapHeight = buff.readUIntLE(16);
var restLength = tagHeader.length - 7;
if (tag.bitmapFormat == 3) {
let restLength = tagHeader.length - 7;
if(tag.bitmapFormat == 3)
{
tag.bitmapColorTableSize = buff.readUInt8();
restLength--;
}
tag.zlibBitmapData = buff.buffer.slice(buff.pointer, buff.pointer + restLength);
buff.pointer += restLength;
break;
}
default:
tag.data = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length);
buff.pointer += tagHeader.length;
@ -275,12 +292,14 @@ function readSWFTags(buff, swf) {
* @api private
*
*/
function readSWFBuff(buff, compressed_buff, next) {
function readSWFBuff(buff, compressed_buff, next)
{
buff.seek(3);// start
if (buff.length < 9) {
if (isSync) throw new Error("Buffer is to small, must be greater than 9 bytes.");
return next(new Error("Buffer is to small, must be greater than 9 bytes."));
if(buff.length < 9)
{
if(isSync) throw new Error('Buffer is to small, must be greater than 9 bytes.');
return next(new Error('Buffer is to small, must be greater than 9 bytes.'));
}
var swf = {
version : buff.readUInt8(),
@ -294,9 +313,12 @@ function readSWFBuff(buff, compressed_buff, next) {
}
, isSync = 'function' !== typeof next;
try {
try
{
swf.tags = readSWFTags(buff, swf);
} catch(e) {
}
catch (e)
{
if(isSync) throw e;
return next(e);
}
@ -310,7 +332,8 @@ function readSWFBuff(buff, compressed_buff, next) {
* @param {Buffer|ArrayBuffer} buff
* @param {Buffer|ArrayBuffer} swf
*/
function concatSWFHeader(buff, swf) {
function concatSWFHeader(buff, swf)
{
return Buffer.concat([swf.slice(0, 8), buff]);
}
@ -321,51 +344,73 @@ function concatSWFHeader(buff, swf) {
* @param {function} callback
*
*/
function uncompress(swf, next) {
function uncompress(swf, next)
{
var compressed_buff = swf.slice(8)
, uncompressed_buff
, isSync = 'function' !== typeof next
, e;
// uncompress buffer
switch( swf[0] ) {
switch(swf[0])
{
case 0x43 : // zlib compressed
if (isSync) {
if(isSync)
{
uncompressed_buff = concatSWFHeader(zlib.unzipSync(compressed_buff), swf);
if(!Buffer.isBuffer(uncompressed_buff))
{
console.log('invalid_buffer');
return null;
}
return readSWFBuff(new SWFBuffer(uncompressed_buff), swf);
}
zlib.inflate(compressed_buff, function(err, buf) {
if(!Buffer.isBuffer(compressed_buff))
{
console.log('invalid_buffer');
return null;
}
zlib.inflate(compressed_buff, function(err, buf)
{
readSWFBuff(new SWFBuffer(buf), swf, next);
});
break;
case 0x46 : // uncompressed
return readSWFBuff(new SWFBuffer( swf ), swf, next);
break;
case 0x5a : // LZMA compressed
var lzmaProperties = compressed_buff.slice(4, 9);
compressed_buff = compressed_buff.slice(9);
var input_stream = new Stream();
input_stream.pos = 0;
input_stream.readByte = function() {
input_stream.readByte = function()
{
return this.pos >= compressed_buff.length ? -1 : compressed_buff[this.pos++];
};
var output_stream = new Stream();
output_stream.buffer = new Buffer(16384);
output_stream.pos = 0;
output_stream.writeByte = function(_byte) {
if (this.pos >= this.buffer.length) {
output_stream.writeByte = function(_byte)
{
if(this.pos >= this.buffer.length)
{
var newBuffer = new Buffer(this.buffer.length * 2);
this.buffer.copy(newBuffer);
this.buffer = newBuffer;
}
this.buffer[this.pos++] = _byte;
};
output_stream.getBuffer = function() {
output_stream.getBuffer = function()
{
// trim buffer
if (this.pos !== this.buffer.length) {
if(this.pos !== this.buffer.length)
{
var newBuffer = new Buffer(this.pos);
this.buffer.copy(newBuffer, 0, 0, this.pos);
this.buffer = newBuffer;
@ -377,17 +422,19 @@ function uncompress(swf, next) {
uncompressed_buff = Buffer.concat([swf.slice(0, 8), output_stream.getBuffer()]);
return readSWFBuff(new SWFBuffer(uncompressed_buff), swf, next);
break;
default :
e = new Error('Unknown SWF compressions');
if (isSync) {
if(isSync)
{
throw e;
} else {
}
else
{
next(e);
}
};
};
}
}
/**
* Check if file is Buffer or ArrayBuffer
@ -396,8 +443,9 @@ function uncompress(swf, next) {
* @api private
*
*/
function isBuffer(b) {
return typeof Buffer !== "undefined" && Buffer.isBuffer(b) || b instanceof ArrayBuffer;
function isBuffer(b)
{
return typeof Buffer !== 'undefined' && Buffer.isBuffer(b) || b instanceof ArrayBuffer;
}
/* Exposes Tags constants */
@ -411,21 +459,30 @@ SWFReader.TAGS = SWFTags;
* @api public
*
*/
SWFReader.read = SWFReader.readSync = function(file, next) {
if (isBuffer(file)) {
SWFReader.read = SWFReader.readSync = function(file, next)
{
if(isBuffer(file))
{
/* File is already a buffer */
return uncompress(file, next);
} else {
}
else
{
/* Get the buffer */
if ('function' === typeof next) {
fs.readFile(file, function(err, swf) {
if ( err ) {
if('function' === typeof next)
{
fs.readFile(file, function(err, swf)
{
if( err )
{
next(err);
return;
}
uncompress(swf, next);
});
} else {
}
else
{
return uncompress(fs.readFileSync(file));
}
}

View File

@ -0,0 +1,225 @@
export class SWFBuffer
{
public static RECORDHEADER_LENTH_FULL: number = 0x3f;
public static EOS: number = 0x00;
public static STYLE_COUNT_EXT: number = 0xFF;
public buffer: Buffer
public pointer: number = 0;
public position: number = 1;
public current: number = 0;
public length: number = 0;
constructor(buffer: Buffer)
{
if(!Buffer.isBuffer(buffer))
{
throw new Error('invalid_buffer');
}
this.buffer = buffer;
this.length = buffer.length;
}
public readUIntLE(bits: number): number
{
let value = 0;
switch(bits)
{
case 16:
value = this.buffer.readUInt16LE(this.pointer);
break;
case 32:
value = this.buffer.readUInt32LE(this.pointer);
break;
}
this.pointer += bits / 8;
return value;
}
public readUInt8(): number
{
return this.buffer.readUInt8(this.pointer++);
}
public readEncodedU32(): number
{
let i = 5;
let result = 0;
let nb = 0;
do
{
result += (nb = this.nextByte());
} while((nb & 128) && --i);
return result;
}
public readRGB(): [ number, number, number ]
{
return [ this.readUInt8(), this.readUInt8(), this.readUInt8() ];
}
public readRGBA(): [ number, number, number, number ]
{
return [ ...this.readRGB(), this.readUInt8() ];
}
public readString(encoding: BufferEncoding): string
{
const init = this.pointer;
while(this.readUInt8() !== SWFBuffer.EOS);
return this.buffer.toString(encoding || 'utf8', init, this.pointer - 1);
}
public readStyleArray(buffer: SWFBuffer, next)
{
let styleArrayCount = buffer.readUInt8();
const styles = [];
if(styleArrayCount === SWFBuffer.STYLE_COUNT_EXT) styleArrayCount = buffer.readUIntLE(16);
for(let i = 0; i < styleArrayCount; i++) styles.push(next(buffer));
return styles;
}
public readFillStyle(buffer: SWFBuffer): { fillStyleType: number, color?: [ number, number, number, number ], bitmapId?: number }
{
const type = buffer.readUInt8();
const fillStyle: { fillStyleType: number, color?: [ number, number, number, number ], bitmapId?: number } = {
fillStyleType: type
};
switch(type)
{
case 0x00:
fillStyle.color = buffer.readRGBA();
break;
case 0x10:
case 0x12:
case 0x13:
console.log('Gradient');
break;
case 0x40:
case 0x41:
case 0x42:
case 0x43:
fillStyle.bitmapId = buffer.readUIntLE(16);
break;
}
return fillStyle;
}
public readLineStyle(buffer: SWFBuffer): { width: number, color: [ number, number, number, number ]}
{
return {
width: buffer.readUIntLE(16)/20,
color: buffer.readRGBA()
};
}
public readShapeRecords(buffer: SWFBuffer)
{
let shapeRecords = null;
const typeFlag = buffer.readBits(1);
let eos = 0;
while((eos = buffer.readBits(5)))
{
if(0 === typeFlag)
{
shapeRecords = {
type: 'STYLECHANGERECORD'
};
}
}
return shapeRecords;
}
public readShapeWithStyle()
{
return {
fillStyles : this.readStyleArray(this, this.readFillStyle),
lineStyles : this.readStyleArray(this, this.readLineStyle),
numFillBits : this.readBits(4),
numLineBits : this.readBits(4),
shapeRecords: this.readShapeRecords(this)
};
}
public readTagCodeAndLength(): { code: number, length: number }
{
if(this.pointer === this.length) return null;
const n = this.readUIntLE(16);
const tagType = n >> 6;
let tagLength = n & SWFBuffer.RECORDHEADER_LENTH_FULL;
if(n === 0) return null;
if(tagLength === SWFBuffer.RECORDHEADER_LENTH_FULL ) tagLength = this.readUIntLE(32);
return { code: tagType, length: tagLength };
}
public readRect(): { x: number, y: number, width: number, height: number }
{
this.start();
const NBits = this.readBits(5);
const Xmin = this.readBits(NBits, true) / 20;
const Xmax = this.readBits(NBits, true) / 20;
const Ymin = this.readBits(NBits, true) / 20;
const Ymax = this.readBits(NBits, true) / 20;
return {
x : Xmin,
y : Ymin,
width : (Xmax > Xmin ? Xmax - Xmin : Xmin - Xmax),
height : (Ymax > Ymin ? Ymax - Ymin : Ymin - Ymax)
};
}
public seek(pos: number): void
{
this.pointer = pos % this.buffer.length;
}
public start(): void
{
this.current = this.nextByte();
this.position = 1;
}
public nextByte(): number
{
return this.pointer > this.buffer.length ? null : this.buffer[ this.pointer++ ];
}
public readBits(b: number, signed: boolean = false): number
{
let n = 0;
let r = 0;
const sign = signed && ++n && ((this.current >> (8 - this.position++)) & 1) ? -1 : 1;
while(n++ < b)
{
if( this.position > 8 ) this.start();
r = (r << 1 ) + ((this.current >> (8 - this.position++)) & 1);
}
return sign * r;
}
}

View File

@ -1,291 +0,0 @@
var RECORDHEADER_LENTH_FULL = 0x3f
// null-character
, EOS = 0x00
, styleCountExt = 0xFF;
function readStyleArray(buffer, next) {
var styleArrayCount = buffer.readUInt8()
, styles = [];
if (styleArrayCount === styleCountExt)
styleArrayCount = buffer.readUIntLE(16);
for (var i = 0; i < styleArrayCount; i++)
styles.push(next(buffer));
return styles;
}
function readFillStyle(buffer) {
var type = buffer.readUInt8()
, fillStyle = {
/**
* 0x00 = solid
* 0x10 = linear gradient fill
* 0x12 = radial gradient fill
* 0x13 = focal radial gradient fill (SWF 8 or later)
* 0x40 = repeating bitmap fill
* 0x41 = clipped bitmap fill
* 0x42 = non-smoothed repeating bitmap
* 0x43 = non-smoothed clipped bitmap
*/
fillStyleType : type
};
switch (type) {
case 0x00:
fillStyle.color = buffer.readRGBA();
break;
case 0x10, 0x12, 0x13:
console.log('Gradient');
break;
case 0x40, 0x41, 0x42, 0x43:
fillStyle.bitmapId = buffer.readUIntLE(16);
break;
}
return fillStyle;
}
function readLineStyle(buffer) {
return {
width: buffer.readUIntLE(16)/20,
color: buffer.readRGBA()
};
}
function readShapeRecords(buffer) {
var shapeRecords = []
, typeFlag = buffer.readBits(1)
, shapeRecord
, eos;
while ((eos = buffer.readBits(5))) {
if (0 === typeFlag) {
shaperecord = {
type: 'STYLECHANGERECORD'
};
}
}
return shapeRecords;
}
/**
*
* Constructor of SWFBuffer object
*
* @param {Buffer} buffer
* @return Instance of SWFBuffer
*/
function SWFBuffer( buffer ) {
if ( !Buffer.isBuffer( buffer ) ) {
throw new Error('Invalid buffer');
}
this.buffer = buffer;
this.pointer = 0;
this.position = 1;
this.current = 0;
this.length = buffer.length;
}
/**
* Reads unsigned 16 or 32 Little Endian Bits
* and advance pointer to next bits / 8 bytes
*
* @param {Number} bits
* @return {Number} Value read from buffer
*/
SWFBuffer.prototype.readUIntLE = function( bits ) {
var value = 0;
try {
value = this.buffer['readUInt' + bits + 'LE'](this.pointer);
this.pointer += bits / 8;
} catch ( e ) {
throw e;
}
return value;
};
/**
* Reads unsigned 8 bit from the buffer
*
* @return {Number} Value read from buffer
*/
SWFBuffer.prototype.readUInt8 = function() {
return this.buffer.readUInt8( this.pointer++ );
};
/**
* Reads 32-bit unsigned integers value encoded (1-5 bytes)
*
* @return {Number} 32-bit unsigned integer
*/
SWFBuffer.prototype.readEncodedU32 = function() {
var i = 5
, result = 0
, nb;
do
result += (nb = this.nextByte());
while((nb & 128) && --i);
return result;
};
/**
* Reads an encoded data from buffer and returns a
* string using the specified character set.
*
* @param {String} encoding - defaults to 'utf8'
* @returns {String} Decoded string
*/
SWFBuffer.prototype.readString = function(encoding) {
var init = this.pointer;
while(this.readUInt8() !== EOS);
return this.buffer.toString(encoding || 'utf8', init, this.pointer - 1);
};
/**
* Reads RGB value
*
* @return {Array} Array of RGB value
*/
SWFBuffer.prototype.readRGB = function() {
return [this.readUInt8(), this.readUInt8(), this.readUInt8()];
};
/**
* Reads RGBA value
*
* @return {Array} Array of RGBA value
*/
SWFBuffer.prototype.readRGBA = function() {
var rgba = this.readRGB();
rgba.push(this.readUInt8());
return rgba;
}
/**
* Reads ShapeWithStyle structure
* used by the DefineShape tag.
*
* @return ShapeWithStyle structure
*/
SWFBuffer.prototype.readShapeWithStyle = function() {
return {
fillStyles : readStyleArray(this, readFillStyle),
lineStyles : readStyleArray(this, readLineStyle),
numFillBits : this.readBits(4),
numLineBits : this.readBits(4),
shapeRecords: readShapeRecords(this)
}
};
/**
* Reads RECORDHEADER from next tag in the buffer
*
* @return {Object} Tag code and length
*/
SWFBuffer.prototype.readTagCodeAndLength = function() {
if (this.pointer === this.length) {
return false;
}
var n = this.readUIntLE(16)
, tagType = n >> 6
, tagLength = n & RECORDHEADER_LENTH_FULL;
if ( n === 0 )
return false;
if ( tagLength === RECORDHEADER_LENTH_FULL )
tagLength = this.readUIntLE(32);
return { code : tagType, length : tagLength };
};
/**
* Reads RECT format
*
* @return {Object} x, y, width and height of the RECT
*/
SWFBuffer.prototype.readRect = function() {
this.start();
var NBits = this.readBits(5)
, Xmin = this.readBits(NBits, true)/20
, Xmax = this.readBits(NBits, true)/20
, Ymin = this.readBits(NBits, true)/20
, Ymax = this.readBits(NBits, true)/20;
return {
x : Xmin,
y : Ymin,
width : (Xmax > Xmin ? Xmax - Xmin : Xmin - Xmax),
height : (Ymax > Ymin ? Ymax - Ymin : Ymin - Ymax)
};
}
/**
* Sets internal pointer to the specified position;
*
* @param {Number} pos
*/
SWFBuffer.prototype.seek = function( pos ) {
this.pointer = pos % this.buffer.length;
};
/**
* Resets position and sets current to next Byte in buffer
*/
SWFBuffer.prototype.start = function() {
this.current = this.nextByte();
this.position = 1;
};
/**
* Gets next Byte in the buffer and Increment internal pointer
*
* @return {Number} Next byte in buffer
*/
SWFBuffer.prototype.nextByte = function() {
return this.pointer > this.buffer.length ? null : this.buffer[ this.pointer++ ];
};
/**
* Reads b bits from current byte in buffer
*
* @param {Number} b
* @return {Number} Bits read from buffer
*/
SWFBuffer.prototype.readBits = function( b, signed ) {
var n = 0
, r = 0
, sign = signed && ++n && ((this.current >> (8-this.position++)) & 1) ? -1 : 1;
while( n++ < b ) {
if ( this.position > 8 ) this.start();
r = (r << 1 ) + ((this.current >> (8-this.position++)) & 1);
}
return sign * r;
};
/* Exposes class */
exports = module.exports = SWFBuffer;

View File

@ -5,7 +5,8 @@
* @param {Mixed} value
*/
function define(name, value) {
function define(name, value)
{
Object.defineProperty(exports, name, {
value : value,
enumerable : true