cyberchef/src/core/operations/AMFEncode.mjs
2026-06-15 19:19:17 +08:00

143 lines
3.5 KiB
JavaScript

/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import "reflect-metadata"; // Required as a shim for the amf library
import { AMF0 } from "@astronautlabs/amf";
const AMF3_MARKER = {
Null: 0x01,
False: 0x02,
True: 0x03,
Double: 0x05,
String: 0x06,
Array: 0x09,
Object: 0x0a
};
/**
* @param {number} value
* @returns {number[]}
*/
function encodeU29(value) {
if (value < 0x80) {
return [value];
}
if (value < 0x4000) {
return [
((value >> 7) & 0x7f) | 0x80,
value & 0x7f
];
}
if (value < 0x200000) {
return [
((value >> 14) & 0x7f) | 0x80,
((value >> 7) & 0x7f) | 0x80,
value & 0x7f
];
}
return [
((value >> 22) & 0x7f) | 0x80,
((value >> 15) & 0x7f) | 0x80,
((value >> 8) & 0x7f) | 0x80,
value & 0xff
];
}
/**
* @param {string} value
* @returns {number[]}
*/
function encodeAMF3Utf8(value) {
const bytes = Utils.strToUtf8ByteArray(value);
return encodeU29((bytes.length << 1) | 1).concat(bytes);
}
/**
* @param {number} value
* @returns {number[]}
*/
function encodeAMF3Double(value) {
const bytes = new Uint8Array(8);
new DataView(bytes.buffer).setFloat64(0, value, false);
return [AMF3_MARKER.Double].concat(Array.from(bytes));
}
/**
* @param {JSON} value
* @returns {number[]}
*/
function encodeAMF3Value(value) {
if (value === null) return [AMF3_MARKER.Null];
if (value === false) return [AMF3_MARKER.False];
if (value === true) return [AMF3_MARKER.True];
if (typeof value === "number") return encodeAMF3Double(value);
if (typeof value === "string") return [AMF3_MARKER.String].concat(encodeAMF3Utf8(value));
if (Array.isArray(value)) {
return [
AMF3_MARKER.Array,
...encodeU29((value.length << 1) | 1),
0x01,
...value.flatMap(encodeAMF3Value)
];
}
const keys = Object.keys(value);
return [
AMF3_MARKER.Object,
...encodeU29((keys.length << 4) | 0x03),
0x01,
...keys.flatMap(encodeAMF3Utf8),
...keys.flatMap(key => encodeAMF3Value(value[key]))
];
}
/**
* AMF Encode operation
*/
class AMFEncode extends Operation {
/**
* AMFEncode constructor
*/
constructor() {
super();
this.name = "AMF Encode";
this.module = "Encodings";
this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.";
this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format";
this.inputType = "JSON";
this.outputType = "ArrayBuffer";
this.args = [
{
name: "Format",
type: "option",
value: ["AMF0", "AMF3"],
defaultIndex: 1
}
];
}
/**
* @param {JSON} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
run(input, args) {
const [format] = args;
const output = format === "AMF0" ?
AMF0.Value.any(input).serialize() :
Uint8Array.from(encodeAMF3Value(input));
return output.buffer;
}
}
export default AMFEncode;