From bf1e708a4cd465b35df6bede05a75f4b81fe1394 Mon Sep 17 00:00:00 2001 From: Brunon Blok <43315279+brun0ne@users.noreply.github.com> Date: Thu, 6 Apr 2023 20:32:50 +0000 Subject: [PATCH 1/3] added PHP Serialize operation --- src/core/operations/PHPSerialize.mjs | 144 ++++++++++++++++++++++++ tests/operations/tests/PHPSerialize.mjs | 112 ++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 src/core/operations/PHPSerialize.mjs create mode 100644 tests/operations/tests/PHPSerialize.mjs diff --git a/src/core/operations/PHPSerialize.mjs b/src/core/operations/PHPSerialize.mjs new file mode 100644 index 00000000..94f42cbb --- /dev/null +++ b/src/core/operations/PHPSerialize.mjs @@ -0,0 +1,144 @@ +/** + * @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;` + } + + /** + * Basic types + */ + if (typeof object !== "object"){ + return `${serializeBasicTypes(object)};`; + } + + /** + * Arrays + */ + else if (object instanceof Array) { + const serializedElements = []; + + for (let i = 0; i < object.length; i++) { + serializedElements.push(`${serialize(i)}${serialize(object[i])}`); + } + + return `a:${object.length}:{${serializedElements.join("")}}` + } + + /** + * Objects + * Note: the output cannot be guaranteed to be in the same order as the input + */ + else if (object instanceof Object) { + 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/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: [] + } + ] + } +]); From 15b426ebb6e18414259f000640f5287c431b5a25 Mon Sep 17 00:00:00 2001 From: Brunon Blok <43315279+brun0ne@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:03:46 +0000 Subject: [PATCH 2/3] clean up code formatting and fix missing entries --- src/core/config/Categories.json | 1 + src/core/operations/PHPSerialize.mjs | 74 +++++++++++++--------------- tests/operations/index.mjs | 1 + 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index ce2f01f5..c95637ef 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -418,6 +418,7 @@ "JPath expression", "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 index 94f42cbb..221af327 100644 --- a/src/core/operations/PHPSerialize.mjs +++ b/src/core/operations/PHPSerialize.mjs @@ -35,7 +35,7 @@ class PHPSerialize extends Operation { run(input, args) { /** * Determines if a number is an integer - * @param {number} value + * @param {number} value * @returns {boolean} */ function isInteger(value) { @@ -44,7 +44,7 @@ class PHPSerialize extends Operation { /** * Serialize basic types - * @param {string | number | boolean} content + * @param {string | number | boolean} content * @returns {string} */ function serializeBasicTypes(content) { @@ -54,40 +54,36 @@ class PHPSerialize extends Operation { "float": "d", "boolean": "b" }; - /** * Booleans * cast to 0 or 1 */ - if (typeof content === "boolean"){ - return `${basicTypes["boolean"]}:${content ? 1 : 0}`; + 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()}` + 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}"`; - + return `${basicTypes.string}:${content.length}:"${content}"`; + /** This should be unreachable */ throw new OperationError(`Encountered a non-implemented type: ${typeof content}`); } /** * Recursively serialize - * @param {*} object + * @param {*} object * @returns {string} */ function serialize(object) { @@ -95,42 +91,38 @@ class PHPSerialize extends Operation { * Null */ if (object == null) { - return `N;` + return `N;`; } - - /** - * Basic types - */ - if (typeof object !== "object"){ + + if (typeof object !== "object") { + /** + * Basic types + */ return `${serializeBasicTypes(object)};`; - } - - /** - * Arrays - */ - else if (object instanceof Array) { + } 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("")}}` - } - - /** - * Objects - * Note: the output cannot be guaranteed to be in the same order as the input - */ - else if (object instanceof Object) { + + 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("")}}` + + return `a:${keys.length}:{${serializedElements.join("")}}`; } /** This should be unreachable */ diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 56f432e0..0bf8fd3b 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -70,6 +70,7 @@ import "./tests/NormaliseUnicode.mjs"; import "./tests/OTP.mjs"; import "./tests/PGP.mjs"; import "./tests/PHP.mjs"; +import "./tests/PHPSerialize.mjs"; import "./tests/ParseIPRange.mjs"; import "./tests/ParseQRCode.mjs"; import "./tests/PEMtoHex.mjs"; From c0e84dcd501d12d0b322b2595f2c2424d6489b0e Mon Sep 17 00:00:00 2001 From: Brunon Blok <43315279+brun0ne@users.noreply.github.com> Date: Sun, 9 Apr 2023 19:06:59 +0000 Subject: [PATCH 3/3] change comments --- src/core/operations/PHPSerialize.mjs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/core/operations/PHPSerialize.mjs b/src/core/operations/PHPSerialize.mjs index 221af327..00fb1380 100644 --- a/src/core/operations/PHPSerialize.mjs +++ b/src/core/operations/PHPSerialize.mjs @@ -61,9 +61,7 @@ class PHPSerialize extends Operation { if (typeof content === "boolean") { return `${basicTypes.boolean}:${content ? 1 : 0}`; } - /** - * Numbers - */ + /* Numbers */ if (typeof content === "number") { if (isInteger(content)) { return `${basicTypes.integer}:${content.toString()}`; @@ -71,9 +69,7 @@ class PHPSerialize extends Operation { return `${basicTypes.float}:${content.toString()}`; } } - /** - * Strings - */ + /* Strings */ if (typeof content === "string") return `${basicTypes.string}:${content.length}:"${content}"`; @@ -87,22 +83,16 @@ class PHPSerialize extends Operation { * @returns {string} */ function serialize(object) { - /** - * Null - */ + /* Null */ if (object == null) { return `N;`; } if (typeof object !== "object") { - /** - * Basic types - */ + /* Basic types */ return `${serializeBasicTypes(object)};`; } else if (object instanceof Array) { - /** - * Arrays - */ + /* Arrays */ const serializedElements = []; for (let i = 0; i < object.length; i++) {