From 4be7f89fd8a6e43e18c65fc57b54e94f60e0f3f9 Mon Sep 17 00:00:00 2001 From: Jarmo van Lenthe Date: Sun, 12 Nov 2017 21:37:29 -0500 Subject: [PATCH 1/8] Add PHP Deserialization. --- src/core/config/Categories.js | 1 + src/core/config/OperationConfig.js | 14 +++ src/core/config/modules/Default.js | 3 +- src/core/operations/PhpSerialization.js | 128 ++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/PhpSerialization.js 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..1d4394c8 --- /dev/null +++ b/src/core/operations/PhpSerialization.js @@ -0,0 +1,128 @@ +/** + * Php Serialization operations. + * This Javascript implementation is based on the Python + * implementation by Armin Ronacher (2016). + * See: https://github.com/mitsuhiko/phpserialize/ + * + * @author Jarmo van Lenthe [github.com/jarmovanlenthe] + * @copyright Crown Copyright 2017 + * @license BSD-3-Clause + * + * @namespace + */ + +const PhpSerialization = { + + /** + * @constant + * @default + */ + OUTPUT_VALID_JSON: true, + + PhpDeserialize: function (input, args) { + function handleInput() { + 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; + } + + function readUntil(until) { + let result = ""; + while (true) { + let char = read(1); + if (char === until) { + break; + } + else { + result += char; + } + } + return result; + + } + + function expect(expect) { + let result = read(expect.length); + if (result !== expect) { + throw "Unexpected input found"; + } + return result; + } + + function handleArray() { + let items = parseInt(readUntil(':')) * 2; + expect('{'); + let result = []; + let isKey = true; + let last_item = null; + for (let idx = 0; idx < items; idx++) { + let item = handleInput(); + if (isKey) { + last_item = item; + isKey = false; + } else { + let numberCheck = last_item.match(/[0-9]+/); + if (args[0] && numberCheck && numberCheck[0].length === last_item.length) + { + result.push('"' + last_item + '": ' + item); + } else { + result.push(last_item + ': ' + 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) !== 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; From f596fe8404ec40d498601646b97acbefbe7c63c9 Mon Sep 17 00:00:00 2001 From: Jarmo van Lenthe Date: Sun, 12 Nov 2017 22:10:28 -0500 Subject: [PATCH 2/8] Add tests for PHP deserialization --- test/index.js | 1 + test/tests/operations/PhpSerialization.js | 68 +++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 test/tests/operations/PhpSerialization.js 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..a38379cd --- /dev/null +++ b/test/tests/operations/PhpSerialization.js @@ -0,0 +1,68 @@ +/** + * PHP Serialization tests. + * + * @author Jarmo van Lenthe + * + * @copyright Crown Copyright 2017 + * @license BSD-3-Clause + */ + +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], + }, + ], + }, +]); From 50a32e90d922129439b6f44a852544fd7408c2fe Mon Sep 17 00:00:00 2001 From: Jarmo van Lenthe Date: Sun, 12 Nov 2017 22:11:16 -0500 Subject: [PATCH 3/8] Reformatted PHP deserialization. --- src/core/operations/PhpSerialization.js | 79 ++++++++++++++----------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/core/operations/PhpSerialization.js b/src/core/operations/PhpSerialization.js index 1d4394c8..7b4298c2 100644 --- a/src/core/operations/PhpSerialization.js +++ b/src/core/operations/PhpSerialization.js @@ -19,6 +19,12 @@ const PhpSerialization = { */ OUTPUT_VALID_JSON: true, + /** + * Deserializes a PHP serialized input + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ PhpDeserialize: function (input, args) { function handleInput() { function read(length) { @@ -39,8 +45,7 @@ const PhpSerialization = { let char = read(1); if (char === until) { break; - } - else { + } else { result += char; } } @@ -57,28 +62,27 @@ const PhpSerialization = { } function handleArray() { - let items = parseInt(readUntil(':')) * 2; - expect('{'); + let items = parseInt(readUntil(":"), 10) * 2; + expect("{"); let result = []; let isKey = true; - let last_item = null; + let lastItem = null; for (let idx = 0; idx < items; idx++) { let item = handleInput(); if (isKey) { - last_item = item; + lastItem = item; isKey = false; } else { - let numberCheck = last_item.match(/[0-9]+/); - if (args[0] && numberCheck && numberCheck[0].length === last_item.length) - { - result.push('"' + last_item + '": ' + item); + let numberCheck = lastItem.match(/[0-9]+/); + if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) { + result.push("\"" + lastItem + "\": " + item); } else { - result.push(last_item + ': ' + item); + result.push(lastItem + ": " + item); } isKey = true; } } - expect('}'); + expect("}"); return result; } @@ -86,41 +90,44 @@ const PhpSerialization = { let kind = read(1).toLowerCase(); switch (kind) { - case 'n': - expect(';'); - return ''; + case "n": + expect(";"); + return ""; - case 'i': - case 'd': - case 'b': - expect(':'); - let data = readUntil(';'); - if (kind === 'b') - return (parseInt(data) !== 0); + 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 'a': - expect(':'); - return '{' + handleArray() + '}'; - - case 's': - expect(':'); - let length = readUntil(':'); - expect('"'); + case "s": { + expect(":"); + let length = readUntil(":"); + expect("\""); let value = read(length); - expect('";'); - if (args[0]) - return '"' + value.replace(/"/g, '\\"') + '"'; - else - return '"' + value + '"'; + expect("\";"); + if (args[0]) { + return "\"" + value.replace(/"/g, "\\\"") + "\""; + } else { + return "\"" + value + "\""; + } + } default: throw "Unknown type: " + kind; } } - let inputPart = input.split(''); + let inputPart = input.split(""); return handleInput(); } }; From 29047c248162d1044373e962d61b22f3271ddfa2 Mon Sep 17 00:00:00 2001 From: Jarmo van Lenthe Date: Sun, 12 Nov 2017 22:20:16 -0500 Subject: [PATCH 4/8] Add JSDoc to helper functions and reformat while true. --- src/core/operations/PhpSerialization.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/core/operations/PhpSerialization.js b/src/core/operations/PhpSerialization.js index 7b4298c2..adaa049a 100644 --- a/src/core/operations/PhpSerialization.js +++ b/src/core/operations/PhpSerialization.js @@ -26,7 +26,16 @@ const PhpSerialization = { * @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++) { @@ -39,9 +48,14 @@ const PhpSerialization = { return result; } + /** + * Read characters from the input until `until` is found. + * @param until + * @returns {string} + */ function readUntil(until) { let result = ""; - while (true) { + for(;;) { let char = read(1); if (char === until) { break; @@ -53,6 +67,11 @@ const PhpSerialization = { } + /** + * 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) { @@ -61,6 +80,10 @@ const PhpSerialization = { return result; } + /** + * Helper function to handle deserialized arrays. + * @returns {Array} + */ function handleArray() { let items = parseInt(readUntil(":"), 10) * 2; expect("{"); From 5399d278751a859f3a8f31dacfb0839878f7791a Mon Sep 17 00:00:00 2001 From: Jarmo van Lenthe Date: Sun, 12 Nov 2017 22:23:38 -0500 Subject: [PATCH 5/8] Add space after for --- src/core/operations/PhpSerialization.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/PhpSerialization.js b/src/core/operations/PhpSerialization.js index adaa049a..6fc92913 100644 --- a/src/core/operations/PhpSerialization.js +++ b/src/core/operations/PhpSerialization.js @@ -55,7 +55,7 @@ const PhpSerialization = { */ function readUntil(until) { let result = ""; - for(;;) { + for (;;) { let char = read(1); if (char === until) { break; From 305956cbe310926e33897967ab38776c8a8d5969 Mon Sep 17 00:00:00 2001 From: Jarmo van Lenthe Date: Mon, 13 Nov 2017 07:15:06 -0500 Subject: [PATCH 6/8] Fix copyright statement --- src/core/operations/PhpSerialization.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/PhpSerialization.js b/src/core/operations/PhpSerialization.js index 6fc92913..eb98a403 100644 --- a/src/core/operations/PhpSerialization.js +++ b/src/core/operations/PhpSerialization.js @@ -5,7 +5,7 @@ * See: https://github.com/mitsuhiko/phpserialize/ * * @author Jarmo van Lenthe [github.com/jarmovanlenthe] - * @copyright Crown Copyright 2017 + * @copyright Armin Ronacher 2016 some rights reserved / Jarmo van Lenthe * @license BSD-3-Clause * * @namespace From ea352e05f0412051bb22ce63dfb4394a319e66a7 Mon Sep 17 00:00:00 2001 From: Jarmo van Lenthe Date: Wed, 15 Nov 2017 16:00:53 -0500 Subject: [PATCH 7/8] Change PHP Serialization operation to Apache-2.0 license. --- src/core/operations/PhpSerialization.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/operations/PhpSerialization.js b/src/core/operations/PhpSerialization.js index eb98a403..3b2d8a20 100644 --- a/src/core/operations/PhpSerialization.js +++ b/src/core/operations/PhpSerialization.js @@ -1,12 +1,12 @@ /** * Php Serialization operations. - * This Javascript implementation is based on the Python - * implementation by Armin Ronacher (2016). + * 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 Armin Ronacher 2016 some rights reserved / Jarmo van Lenthe - * @license BSD-3-Clause + * @copyright Jarmo van Lenthe + * @license Apache-2.0 * * @namespace */ From 00074f914fecd17757d635a26ab4176867f7b819 Mon Sep 17 00:00:00 2001 From: Jarmo van Lenthe Date: Thu, 16 Nov 2017 07:37:11 -0500 Subject: [PATCH 8/8] Change test to Apache-2.0 license --- test/tests/operations/PhpSerialization.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/operations/PhpSerialization.js b/test/tests/operations/PhpSerialization.js index a38379cd..8d745df9 100644 --- a/test/tests/operations/PhpSerialization.js +++ b/test/tests/operations/PhpSerialization.js @@ -4,7 +4,7 @@ * @author Jarmo van Lenthe * * @copyright Crown Copyright 2017 - * @license BSD-3-Clause + * @license Apache-2.0 */ import TestRegister from "../../TestRegister.js";