diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js
index f04b5fd9..09f0187c 100755
--- a/src/core/config/Categories.js
+++ b/src/core/config/Categories.js
@@ -66,6 +66,7 @@ const Categories = [
"Encode text",
"Decode text",
"Swap endianness",
+ "PHP Deserialize",
]
},
{
diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js
index 9caa4f91..469a98c1 100755
--- a/src/core/config/OperationConfig.js
+++ b/src/core/config/OperationConfig.js
@@ -35,6 +35,7 @@ import StrUtils from "../operations/StrUtils.js";
import Tidy from "../operations/Tidy.js";
import Unicode from "../operations/Unicode.js";
import URL_ from "../operations/URL.js";
+import PhpSerialization from "../operations/PhpSerialization.js";
/**
@@ -3845,6 +3846,19 @@ const OperationConfig = {
}
]
},
+ "PHP Deserialize": {
+ module: "Default",
+ description: "PHP Deserialize a given input.
This function does not support object
tags.
Output valid JSON: JSON doesn't support integers as keys, where as PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes.",
+ inputType: "string",
+ outputType: "string",
+ args: [
+ {
+ name: "Output valid JSON",
+ type: "boolean",
+ value: PhpSerialization.OUTPUT_VALID_JSON
+ }
+ ]
+ },
};
diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js
index 682db223..8c13cfd2 100644
--- a/src/core/config/modules/Default.js
+++ b/src/core/config/modules/Default.js
@@ -27,7 +27,7 @@ import StrUtils from "../../operations/StrUtils.js";
import Tidy from "../../operations/Tidy.js";
import Unicode from "../../operations/Unicode.js";
import UUID from "../../operations/UUID.js";
-
+import PhpSerialization from "../../operations/PhpSerialization";
/**
* Default module.
@@ -155,6 +155,7 @@ OpModules.Default = {
"Conditional Jump": FlowControl.runCondJump,
"Return": FlowControl.runReturn,
"Comment": FlowControl.runComment,
+ "PHP Deserialize": PhpSerialization.PhpDeserialize,
/*
diff --git a/src/core/operations/PhpSerialization.js b/src/core/operations/PhpSerialization.js
new file mode 100644
index 00000000..3b2d8a20
--- /dev/null
+++ b/src/core/operations/PhpSerialization.js
@@ -0,0 +1,158 @@
+/**
+ * Php Serialization operations.
+ * This Javascript implementation is based on the Python implementation by
+ * Armin Ronacher (2016), who released it under the 3-Clause BSD license.
+ * See: https://github.com/mitsuhiko/phpserialize/
+ *
+ * @author Jarmo van Lenthe [github.com/jarmovanlenthe]
+ * @copyright Jarmo van Lenthe
+ * @license Apache-2.0
+ *
+ * @namespace
+ */
+
+const PhpSerialization = {
+
+ /**
+ * @constant
+ * @default
+ */
+ OUTPUT_VALID_JSON: true,
+
+ /**
+ * Deserializes a PHP serialized input
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ PhpDeserialize: function (input, args) {
+ /**
+ * Recursive method for deserializing.
+ * @returns {*}
+ */
+ function handleInput() {
+ /**
+ * Read `length` characters from the input, shifting them out the input.
+ * @param length
+ * @returns {string}
+ */
+ function read(length) {
+ let result = "";
+ for (let idx = 0; idx < length; idx++) {
+ let char = inputPart.shift();
+ if (char === undefined) {
+ throw "End of input reached before end of script";
+ }
+ result += char;
+ }
+ return result;
+ }
+
+ /**
+ * Read characters from the input until `until` is found.
+ * @param until
+ * @returns {string}
+ */
+ function readUntil(until) {
+ let result = "";
+ for (;;) {
+ let char = read(1);
+ if (char === until) {
+ break;
+ } else {
+ result += char;
+ }
+ }
+ return result;
+
+ }
+
+ /**
+ * Read characters from the input that must be equal to `expect`
+ * @param expect
+ * @returns {string}
+ */
+ function expect(expect) {
+ let result = read(expect.length);
+ if (result !== expect) {
+ throw "Unexpected input found";
+ }
+ return result;
+ }
+
+ /**
+ * Helper function to handle deserialized arrays.
+ * @returns {Array}
+ */
+ function handleArray() {
+ let items = parseInt(readUntil(":"), 10) * 2;
+ expect("{");
+ let result = [];
+ let isKey = true;
+ let lastItem = null;
+ for (let idx = 0; idx < items; idx++) {
+ let item = handleInput();
+ if (isKey) {
+ lastItem = item;
+ isKey = false;
+ } else {
+ let numberCheck = lastItem.match(/[0-9]+/);
+ if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) {
+ result.push("\"" + lastItem + "\": " + item);
+ } else {
+ result.push(lastItem + ": " + item);
+ }
+ isKey = true;
+ }
+ }
+ expect("}");
+ return result;
+ }
+
+
+ let kind = read(1).toLowerCase();
+
+ switch (kind) {
+ case "n":
+ expect(";");
+ return "";
+
+ case "i":
+ case "d":
+ case "b": {
+ expect(":");
+ let data = readUntil(";");
+ if (kind === "b") {
+ return (parseInt(data, 10) !== 0);
+ }
+ return data;
+ }
+
+ case "a":
+ expect(":");
+ return "{" + handleArray() + "}";
+
+ case "s": {
+ expect(":");
+ let length = readUntil(":");
+ expect("\"");
+ let value = read(length);
+ expect("\";");
+ if (args[0]) {
+ return "\"" + value.replace(/"/g, "\\\"") + "\"";
+ } else {
+ return "\"" + value + "\"";
+ }
+ }
+
+ default:
+ throw "Unknown type: " + kind;
+ }
+ }
+
+ let inputPart = input.split("");
+ return handleInput();
+ }
+};
+
+export default PhpSerialization;
diff --git a/test/index.js b/test/index.js
index 773a5b14..9fa4dcdc 100644
--- a/test/index.js
+++ b/test/index.js
@@ -27,6 +27,7 @@ import "./tests/operations/MorseCode.js";
import "./tests/operations/MS.js";
import "./tests/operations/StrUtils.js";
import "./tests/operations/SeqUtils.js";
+import "./tests/operations/PhpSerialization.js";
let allTestsPassing = true;
diff --git a/test/tests/operations/PhpSerialization.js b/test/tests/operations/PhpSerialization.js
new file mode 100644
index 00000000..8d745df9
--- /dev/null
+++ b/test/tests/operations/PhpSerialization.js
@@ -0,0 +1,68 @@
+/**
+ * PHP Serialization tests.
+ *
+ * @author Jarmo van Lenthe
+ *
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../TestRegister.js";
+
+TestRegister.addTests([
+ {
+ name: "PHP Deserialize empty array",
+ input: "a:0:{}",
+ expectedOutput: "{}",
+ recipeConfig: [
+ {
+ op: "PHP Deserialize",
+ args: [true],
+ },
+ ],
+ },
+ {
+ name: "PHP Deserialize integer",
+ input: "i:10;",
+ expectedOutput: "10",
+ recipeConfig: [
+ {
+ op: "PHP Deserialize",
+ args: [true],
+ },
+ ],
+ },
+ {
+ name: "PHP Deserialize string",
+ input: "s:17:\"PHP Serialization\";",
+ expectedOutput: "\"PHP Serialization\"",
+ recipeConfig: [
+ {
+ op: "PHP Deserialize",
+ args: [true],
+ },
+ ],
+ },
+ {
+ name: "PHP Deserialize array (JSON)",
+ input: "a:2:{s:1:\"a\";i:10;i:0;a:1:{s:2:\"ab\";b:1;}}",
+ expectedOutput: "{\"a\": 10,\"0\": {\"ab\": true}}",
+ recipeConfig: [
+ {
+ op: "PHP Deserialize",
+ args: [true],
+ },
+ ],
+ },
+ {
+ name: "PHP Deserialize array (non-JSON)",
+ input: "a:2:{s:1:\"a\";i:10;i:0;a:1:{s:2:\"ab\";b:1;}}",
+ expectedOutput: "{\"a\": 10,0: {\"ab\": true}}",
+ recipeConfig: [
+ {
+ op: "PHP Deserialize",
+ args: [false],
+ },
+ ],
+ },
+]);