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/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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"; From fe8049199a1a08026c52ea6bcba53e00bb604944 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 24 Nov 2017 16:32:11 +0000 Subject: [PATCH 9/9] Moved PhpDeserialize.js to PHP.js to encompass possible future PHP-related ops --- src/core/config/Categories.js | 2 +- src/core/config/OperationConfig.js | 6 +++--- src/core/config/modules/Default.js | 4 ++-- .../{PhpSerialization.js => PHP.js} | 20 ++++++++++--------- test/index.js | 2 +- .../{PhpSerialization.js => PHP.js} | 2 +- 6 files changed, 19 insertions(+), 17 deletions(-) rename src/core/operations/{PhpSerialization.js => PHP.js} (91%) rename test/tests/operations/{PhpSerialization.js => PHP.js} (98%) diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js index 09f0187c..3bc672b7 100755 --- a/src/core/config/Categories.js +++ b/src/core/config/Categories.js @@ -66,7 +66,6 @@ const Categories = [ "Encode text", "Decode text", "Swap endianness", - "PHP Deserialize", ] }, { @@ -289,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 469a98c1..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"; @@ -35,7 +36,6 @@ 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"; /** @@ -3848,14 +3848,14 @@ 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.", + 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: PhpSerialization.OUTPUT_VALID_JSON + value: PHP.OUTPUT_VALID_JSON } ] }, diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js index 8c13cfd2..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"; @@ -27,7 +28,6 @@ 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,7 +155,7 @@ OpModules.Default = { "Conditional Jump": FlowControl.runCondJump, "Return": FlowControl.runReturn, "Comment": FlowControl.runComment, - "PHP Deserialize": PhpSerialization.PhpDeserialize, + "PHP Deserialize": PHP.runDeserialize, /* diff --git a/src/core/operations/PhpSerialization.js b/src/core/operations/PHP.js similarity index 91% rename from src/core/operations/PhpSerialization.js rename to src/core/operations/PHP.js index 3b2d8a20..e4bb0b5b 100644 --- a/src/core/operations/PhpSerialization.js +++ b/src/core/operations/PHP.js @@ -1,8 +1,5 @@ /** - * 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/ + * PHP operations. * * @author Jarmo van Lenthe [github.com/jarmovanlenthe] * @copyright Jarmo van Lenthe @@ -10,8 +7,7 @@ * * @namespace */ - -const PhpSerialization = { +const PHP = { /** * @constant @@ -20,12 +16,17 @@ const PhpSerialization = { OUTPUT_VALID_JSON: true, /** - * Deserializes a PHP serialized input + * 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} */ - PhpDeserialize: function (input, args) { + runDeserialize: function (input, args) { /** * Recursive method for deserializing. * @returns {*} @@ -153,6 +154,7 @@ const PhpSerialization = { let inputPart = input.split(""); return handleInput(); } + }; -export default PhpSerialization; +export default PHP; diff --git a/test/index.js b/test/index.js index 9fa4dcdc..748e1103 100644 --- a/test/index.js +++ b/test/index.js @@ -25,9 +25,9 @@ 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"; -import "./tests/operations/PhpSerialization.js"; let allTestsPassing = true; diff --git a/test/tests/operations/PhpSerialization.js b/test/tests/operations/PHP.js similarity index 98% rename from test/tests/operations/PhpSerialization.js rename to test/tests/operations/PHP.js index 8d745df9..a42ee430 100644 --- a/test/tests/operations/PhpSerialization.js +++ b/test/tests/operations/PHP.js @@ -1,5 +1,5 @@ /** - * PHP Serialization tests. + * PHP tests. * * @author Jarmo van Lenthe *