diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index bae41f9e..71b311e6 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -470,6 +470,7 @@
"Jq",
"CSS selector",
"PHP Deserialize",
+ "PHP Serialize",
"Microsoft Script Decoder",
"Strip HTML tags",
"Diff",
diff --git a/src/core/operations/PHPSerialize.mjs b/src/core/operations/PHPSerialize.mjs
new file mode 100644
index 00000000..00fb1380
--- /dev/null
+++ b/src/core/operations/PHPSerialize.mjs
@@ -0,0 +1,126 @@
+/**
+ * @author brun0ne [brunonblok@gmail.com]
+ * @copyright Crown Copyright 2023
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * PHP Serialize operation
+ */
+class PHPSerialize extends Operation {
+
+ /**
+ * PHPSerialize constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "PHP Serialize";
+ this.module = "Default";
+ this.description = "Performs PHP serialization on JSON data.
This function does not support object
tags.
Since PHP doesn't distinguish dicts and arrays, this operation is not always symmetric to PHP Deserialize
.
Example:
[5,"abc",true]
becomes
a:3:{i:0;i:5;i:1;s:3:"abc";i:2;b:1;}";
+ this.infoURL = "https://www.phpinternalsbook.com/php5/classes_objects/serialization.html";
+ this.inputType = "JSON";
+ this.outputType = "string";
+ this.args = [];
+ }
+
+ /**
+ * @param {JSON} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ /**
+ * Determines if a number is an integer
+ * @param {number} value
+ * @returns {boolean}
+ */
+ function isInteger(value) {
+ return typeof value === "number" && parseInt(value.toString(), 10) === value;
+ }
+
+ /**
+ * Serialize basic types
+ * @param {string | number | boolean} content
+ * @returns {string}
+ */
+ function serializeBasicTypes(content) {
+ const basicTypes = {
+ "string": "s",
+ "integer": "i",
+ "float": "d",
+ "boolean": "b"
+ };
+ /**
+ * Booleans
+ * cast to 0 or 1
+ */
+ if (typeof content === "boolean") {
+ return `${basicTypes.boolean}:${content ? 1 : 0}`;
+ }
+ /* Numbers */
+ if (typeof content === "number") {
+ if (isInteger(content)) {
+ return `${basicTypes.integer}:${content.toString()}`;
+ } else {
+ return `${basicTypes.float}:${content.toString()}`;
+ }
+ }
+ /* Strings */
+ if (typeof content === "string")
+ return `${basicTypes.string}:${content.length}:"${content}"`;
+
+ /** This should be unreachable */
+ throw new OperationError(`Encountered a non-implemented type: ${typeof content}`);
+ }
+
+ /**
+ * Recursively serialize
+ * @param {*} object
+ * @returns {string}
+ */
+ function serialize(object) {
+ /* Null */
+ if (object == null) {
+ return `N;`;
+ }
+
+ if (typeof object !== "object") {
+ /* Basic types */
+ return `${serializeBasicTypes(object)};`;
+ } else if (object instanceof Array) {
+ /* Arrays */
+ const serializedElements = [];
+
+ for (let i = 0; i < object.length; i++) {
+ serializedElements.push(`${serialize(i)}${serialize(object[i])}`);
+ }
+
+ return `a:${object.length}:{${serializedElements.join("")}}`;
+ } else if (object instanceof Object) {
+ /**
+ * Objects
+ * Note: the output cannot be guaranteed to be in the same order as the input
+ */
+ const serializedElements = [];
+ const keys = Object.keys(object);
+
+ for (const key of keys) {
+ serializedElements.push(`${serialize(key)}${serialize(object[key])}`);
+ }
+
+ return `a:${keys.length}:{${serializedElements.join("")}}`;
+ }
+
+ /** This should be unreachable */
+ throw new OperationError(`Encountered a non-implemented type: ${typeof object}`);
+ }
+
+ return serialize(input);
+ }
+}
+
+export default PHPSerialize;
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index 9f4dfbcb..bb7016bb 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -126,6 +126,7 @@ import "./tests/ParseUDP.mjs";
import "./tests/PEMtoHex.mjs";
import "./tests/PGP.mjs";
import "./tests/PHP.mjs";
+import "./tests/PHPSerialize.mjs";
import "./tests/PowerSet.mjs";
import "./tests/Protobuf.mjs";
import "./tests/PubKeyFromCert.mjs";
diff --git a/tests/operations/tests/PHPSerialize.mjs b/tests/operations/tests/PHPSerialize.mjs
new file mode 100644
index 00000000..fa6e87c5
--- /dev/null
+++ b/tests/operations/tests/PHPSerialize.mjs
@@ -0,0 +1,112 @@
+/**
+ * PHP Serialization tests.
+ *
+ * @author brun0ne [brunonblok@gmail.com]
+ *
+ * @copyright Crown Copyright 2023
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "PHP Serialize empty array",
+ input: "[]",
+ expectedOutput: "a:0:{}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize empty object",
+ input: "{}",
+ expectedOutput: "a:0:{}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize null",
+ input: "null",
+ expectedOutput: "N;",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize integer",
+ input: "10",
+ expectedOutput: "i:10;",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize float",
+ input: "14.523",
+ expectedOutput: "d:14.523;",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize boolean",
+ input: "[true, false]",
+ expectedOutput: "a:2:{i:0;b:1;i:1;b:0;}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize string",
+ input: "\"Test string to serialize\"",
+ expectedOutput: "s:24:\"Test string to serialize\";",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize object",
+ input: "{\"a\": 10,\"0\": {\"ab\": true}}",
+ expectedOutput: "a:2:{s:1:\"0\";a:1:{s:2:\"ab\";b:1;}s:1:\"a\";i:10;}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize array",
+ input: "[1,\"abc\",true,{\"x\":1,\"y\":2}]",
+ expectedOutput: "a:4:{i:0;i:1;i:1;s:3:\"abc\";i:2;b:1;i:3;a:2:{s:1:\"x\";i:1;s:1:\"y\";i:2;}}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ }
+]);