UNPKG

libamf

Version:

Action Message Format library for node.js

405 lines (319 loc) 9.73 kB
'use strict'; const Utils = require('../utils/Utils'); const Markers = require('./Markers').AMF0; const AbstractAMF = require('./AbstractAMF'); const ByteArray = require('bytearray-node'); const AMF3 = require('./AMF3'); const XML = require('./flash/XML'); class AMF0 extends AbstractAMF { constructor(core) { super(core); /** * An array containing objects that are used as references. * @type {Array} */ this.references = null; /** * Whether or not objects will be delegated to AMF3 format * @type {Boolean} */ this.avmPlus = null; /** * @type {AMF3} */ this.amf3 = null; /** * @type {ByteArray} */ this.byteArray = null; this.reset(); } get markers() { return Markers; } resetReferences() { this.references = []; if(this.amf3) { this.amf3.resetReferences(); } } reset() { this.avmPlus = false; this.resetReferences(); this.amf3 = new AMF3(this.core); if(this.byteArray) { this.byteArray.reset(); } else { this.byteArray = new ByteArray(); } } read(marker) { if(!marker) { marker = this.readByte(); } switch(marker) { case Markers.NULL: return null; break case Markers.UNDEFINED: return undefined; break case Markers.STRING: return this.readString(); break case Markers.LONG_STRING: return this.readString(true); break case Markers.NUMBER: return this.readDouble(); break case Markers.BOOLEAN: return this.readBoolean(); break case Markers.REFERENCE: return this.readReference(); break case Markers.STRICT_ARRAY: return this.readStrictArray(); break case Markers.ECMA_ARRAY: return this.readECMAArray(); break case Markers.OBJECT: return this.readObject(); break case Markers.TYPED_OBJECT: return this.readObject(true); break case Markers.DATE: return this.readDate(); break case Markers.XML_DOC: return this.readXML(); break case Markers.AVMPLUS: return this.readAVMPlus(); break } } /** * @param {Boolean} long - Whether or not the string being read is a long string */ readString(long = false) { const length = long ? this.readUnsignedInt() : this.readUnsignedShort(); return this.readUTFBytes(length); } readReference() { const index = this.readUnsignedShort(); return this.references[index]; } /** * @returns {Array} */ readStrictArray() { const length = this.readUnsignedInt(); const arr = []; this.references.push(arr); for(var i = 0; i < length; i++) { arr.push(this.read()); } return arr; } /** * @returns {Map} */ readECMAArray() { const length = this.readUnsignedShort(); const map = new Map(); this.references.push(map); for(var i = 0; i < length; i++) { const key = this.readUTF(); const value = this.read(); map.set(key, value); } return map; } /** * @param {Boolean} isTyped - Whether or not the object is a typed one (class instance) * @returns {Object} */ readObject(isTyped = false) { var name; var obj = {}; if(isTyped) { name = this.readUTF(); const cls = this.getClass(name); obj = cls ? new cls : Utils.constructClass(name); } this.references.push(obj); var marker = this.readByte(); while(marker !== Markers.OBJECT_END) { this.byteArray.position--; const key = this.readUTF(); const value = this.read(); obj[key] = value; marker = this.readByte(); } return obj; } /** * @returns {Date} */ readDate() { const time = this.readDouble(); const offset = this.readShort(); // todo: make use of offset const date = new Date(time); this.references.push(date); return date; } /** * @returns {XML} */ readXML() { return XML.parse(this.readString(true), false); } /** * @returns {*} */ readAVMPlus() { this.amf3.byteArray = this.byteArray; const res = this.amf3.read(); return res; } getReference(data) { const index = this.references.indexOf(data); if(index > -1) { return index; } this.references.push(data); return false; } write(data) { if(data == null) { this.writeByte(data === null ? Markers.NULL : Markers.UNDEFINED); return this.byteArray.buffer; } const type = typeof data; switch (type) { case 'string': this.writeString(data); break case 'number': this.writeDouble(Number(data)); break case 'bigint': this.writeString(data.toString()); break case 'boolean': this.writeBoolean(data); break case 'object': const index = this.getReference(data); if (index !== false) { this.writeReference(index); break } if (data instanceof Date) { this.writeDate(data); } else if(this.avmPlus) { this.writeAVMPlus(data); } else if (data instanceof Map || (Utils.isAssociativeArray(data))) { this.writeECMAArray(data); } else if (data instanceof Array) { this.writeStrictArray(data); } else if(data instanceof XML) { this.writeXML(data); } else { this.writeObject(data); } break; default: throw new Error('Invalid data type: ' + type); } return this.byteArray.buffer; } /** * @param {String} data * @param {Boolean} writeType * @param {Boolean} [forceLong=false] */ writeString(data, writeType = true, forceLong = false) { data = String(data); if (forceLong || data.length > 65535) { if (writeType) { this.writeByte(Markers.AMF0.LONG_STRING); } this.writeUnsignedInt(data.length); } else { if (writeType) { this.writeByte(Markers.STRING); } this.writeUnsignedShort(data.length); } this.writeUTFBytes(data); } /** * @param {Number} data */ writeDouble(data) { this.writeByte(Markers.NUMBER); this.super_writeDouble(data); } /** * @param {Boolean} data * @param {Boolean} [writeType=true] */ writeBoolean(data, writeType = true) { if(writeType) { this.writeByte(Markers.BOOLEAN); } this.super_writeBoolean(data); } /** * @param {Date} data */ writeDate(data) { this.writeByte(Markers.DATE); this.super_writeDouble(data.getTime()); this.writeShort(data.getTimezoneOffset()); } /** * @param {Number} data - Reference index */ writeReference(data) { this.writeByte(Markers.REFERENCE); this.writeUnsignedShort(data); } /** * @param {Array} data */ writeStrictArray(data) { this.writeByte(Markers.STRICT_ARRAY); this.writeUnsignedInt(data.length); for(var i in data) { this.write(data[i]); } } /** * @param {XML} data */ writeXML(data) { this.writeByte(Markers.XML_DOC); this.writeString(data.stringify(), false, true); } /** * @param {Map|Array} data */ writeECMAArray(data) { this.writeByte(Markers.ECMA_ARRAY); if(data instanceof Map) { this.writeUnsignedShort(data.size); for(const [key, value] of data) { this.writeString(key, false); this.write(value); } } else { // AMF0 uses 0 length for associative arrays, we write the length anyway this.writeUnsignedShort(Object.keys(data).length) for (const key in data) { this.writeString(key, false) this.write(data[key]) } } this.writeObjectEnd(); } /** * @param {Object} data */ writeObject(data) { const className = this.getClassName(data); if(className !== 'Object') { this.writeByte(Markers.TYPED_OBJECT); this.writeString(className, false); } else { this.writeByte(Markers.OBJECT); } for(var i in data) { this.writeString(i, false); this.write(data[i]); } this.writeObjectEnd(); } writeObjectEnd() { this.writeByte(Markers.OBJECT_END); } /** * Write an object using AMF3 * @param {Object} data */ writeAVMPlus(data) { this.writeByte(Markers.AVMPLUS); this.amf3.byteArray = this.byteArray; this.amf3.write(data); } } module.exports = AMF0;