diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js
index f04b5fd9..3bc672b7 100755
--- a/src/core/config/Categories.js
+++ b/src/core/config/Categories.js
@@ -288,6 +288,7 @@ const Categories = [
"XPath expression",
"JPath expression",
"CSS selector",
+ "PHP Deserialize",
"Microsoft Script Decoder",
"Strip HTML tags",
"Diff",
diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js
index 9caa4f91..43072558 100755
--- a/src/core/config/OperationConfig.js
+++ b/src/core/config/OperationConfig.js
@@ -26,6 +26,7 @@ import JS from "../operations/JS.js";
import MAC from "../operations/MAC.js";
import MorseCode from "../operations/MorseCode.js";
import NetBIOS from "../operations/NetBIOS.js";
+import PHP from "../operations/PHP.js";
import PublicKey from "../operations/PublicKey.js";
import Punycode from "../operations/Punycode.js";
import Rotate from "../operations/Rotate.js";
@@ -3845,6 +3846,19 @@ const OperationConfig = {
}
]
},
+ "PHP Deserialize": {
+ module: "Default",
+ description: "Deserializes PHP serialized data, outputting keyed arrays as JSON.
This function does not support object
tags.
Example:
a:2:{s:1:"a";i:10;i:0;a:1:{s:2:"ab";b:1;}}
becomes
{"a": 10,0: {"ab": true}}
Output valid JSON: JSON doesn't support integers as keys, whereas 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: PHP.OUTPUT_VALID_JSON
+ }
+ ]
+ },
};
diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js
index 682db223..dec015a5 100644
--- a/src/core/config/modules/Default.js
+++ b/src/core/config/modules/Default.js
@@ -20,6 +20,7 @@ import NetBIOS from "../../operations/NetBIOS.js";
import Numberwang from "../../operations/Numberwang.js";
import OS from "../../operations/OS.js";
import OTP from "../../operations/OTP.js";
+import PHP from "../../operations/PHP.js";
import QuotedPrintable from "../../operations/QuotedPrintable.js";
import Rotate from "../../operations/Rotate.js";
import SeqUtils from "../../operations/SeqUtils.js";
@@ -28,7 +29,6 @@ import Tidy from "../../operations/Tidy.js";
import Unicode from "../../operations/Unicode.js";
import UUID from "../../operations/UUID.js";
-
/**
* Default module.
*
@@ -155,6 +155,7 @@ OpModules.Default = {
"Conditional Jump": FlowControl.runCondJump,
"Return": FlowControl.runReturn,
"Comment": FlowControl.runComment,
+ "PHP Deserialize": PHP.runDeserialize,
/*
diff --git a/src/core/operations/PHP.js b/src/core/operations/PHP.js
new file mode 100644
index 00000000..e4bb0b5b
--- /dev/null
+++ b/src/core/operations/PHP.js
@@ -0,0 +1,160 @@
+/**
+ * PHP operations.
+ *
+ * @author Jarmo van Lenthe [github.com/jarmovanlenthe]
+ * @copyright Jarmo van Lenthe
+ * @license Apache-2.0
+ *
+ * @namespace
+ */
+const PHP = {
+
+ /**
+ * @constant
+ * @default
+ */
+ OUTPUT_VALID_JSON: true,
+
+ /**
+ * PHP Deserialize operation.
+ *
+ * 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/
+ *
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ runDeserialize: 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 PHP;
diff --git a/test/index.js b/test/index.js
index 773a5b14..748e1103 100644
--- a/test/index.js
+++ b/test/index.js
@@ -25,6 +25,7 @@ import "./tests/operations/Hash.js";
import "./tests/operations/Image.js";
import "./tests/operations/MorseCode.js";
import "./tests/operations/MS.js";
+import "./tests/operations/PHP.js";
import "./tests/operations/StrUtils.js";
import "./tests/operations/SeqUtils.js";
diff --git a/test/tests/operations/PHP.js b/test/tests/operations/PHP.js
new file mode 100644
index 00000000..a42ee430
--- /dev/null
+++ b/test/tests/operations/PHP.js
@@ -0,0 +1,68 @@
+/**
+ * PHP 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],
+ },
+ ],
+ },
+]);