Skip to content

Commit f66cd8f

Browse files
committed
Merge branch 'jarmovanlenthe-master'
2 parents c23d7fd + fe80491 commit f66cd8f

File tree

6 files changed

+246
-1
lines changed

6 files changed

+246
-1
lines changed

src/core/config/Categories.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ const Categories = [
288288
"XPath expression",
289289
"JPath expression",
290290
"CSS selector",
291+
"PHP Deserialize",
291292
"Microsoft Script Decoder",
292293
"Strip HTML tags",
293294
"Diff",

src/core/config/OperationConfig.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import JS from "../operations/JS.js";
2626
import MAC from "../operations/MAC.js";
2727
import MorseCode from "../operations/MorseCode.js";
2828
import NetBIOS from "../operations/NetBIOS.js";
29+
import PHP from "../operations/PHP.js";
2930
import PublicKey from "../operations/PublicKey.js";
3031
import Punycode from "../operations/Punycode.js";
3132
import Rotate from "../operations/Rotate.js";
@@ -3845,6 +3846,19 @@ const OperationConfig = {
38453846
}
38463847
]
38473848
},
3849+
"PHP Deserialize": {
3850+
module: "Default",
3851+
description: "Deserializes PHP serialized data, outputting keyed arrays as JSON.<br><br>This function does not support <code>object</code> tags.<br><br>Example:<br><code>a:2:{s:1:&quot;a&quot;;i:10;i:0;a:1:{s:2:&quot;ab&quot;;b:1;}}</code><br>becomes<br><code>{&quot;a&quot;: 10,0: {&quot;ab&quot;: true}}</code><br><br><u>Output valid JSON:</u> JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes.",
3852+
inputType: "string",
3853+
outputType: "string",
3854+
args: [
3855+
{
3856+
name: "Output valid JSON",
3857+
type: "boolean",
3858+
value: PHP.OUTPUT_VALID_JSON
3859+
}
3860+
]
3861+
},
38483862
};
38493863

38503864

src/core/config/modules/Default.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import NetBIOS from "../../operations/NetBIOS.js";
2020
import Numberwang from "../../operations/Numberwang.js";
2121
import OS from "../../operations/OS.js";
2222
import OTP from "../../operations/OTP.js";
23+
import PHP from "../../operations/PHP.js";
2324
import QuotedPrintable from "../../operations/QuotedPrintable.js";
2425
import Rotate from "../../operations/Rotate.js";
2526
import SeqUtils from "../../operations/SeqUtils.js";
@@ -28,7 +29,6 @@ import Tidy from "../../operations/Tidy.js";
2829
import Unicode from "../../operations/Unicode.js";
2930
import UUID from "../../operations/UUID.js";
3031

31-
3232
/**
3333
* Default module.
3434
*
@@ -155,6 +155,7 @@ OpModules.Default = {
155155
"Conditional Jump": FlowControl.runCondJump,
156156
"Return": FlowControl.runReturn,
157157
"Comment": FlowControl.runComment,
158+
"PHP Deserialize": PHP.runDeserialize,
158159

159160

160161
/*

src/core/operations/PHP.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/**
2+
* PHP operations.
3+
*
4+
* @author Jarmo van Lenthe [github.com/jarmovanlenthe]
5+
* @copyright Jarmo van Lenthe
6+
* @license Apache-2.0
7+
*
8+
* @namespace
9+
*/
10+
const PHP = {
11+
12+
/**
13+
* @constant
14+
* @default
15+
*/
16+
OUTPUT_VALID_JSON: true,
17+
18+
/**
19+
* PHP Deserialize operation.
20+
*
21+
* This Javascript implementation is based on the Python implementation by
22+
* Armin Ronacher (2016), who released it under the 3-Clause BSD license.
23+
* See: https://github.com/mitsuhiko/phpserialize/
24+
*
25+
* @param {string} input
26+
* @param {Object[]} args
27+
* @returns {string}
28+
*/
29+
runDeserialize: function (input, args) {
30+
/**
31+
* Recursive method for deserializing.
32+
* @returns {*}
33+
*/
34+
function handleInput() {
35+
/**
36+
* Read `length` characters from the input, shifting them out the input.
37+
* @param length
38+
* @returns {string}
39+
*/
40+
function read(length) {
41+
let result = "";
42+
for (let idx = 0; idx < length; idx++) {
43+
let char = inputPart.shift();
44+
if (char === undefined) {
45+
throw "End of input reached before end of script";
46+
}
47+
result += char;
48+
}
49+
return result;
50+
}
51+
52+
/**
53+
* Read characters from the input until `until` is found.
54+
* @param until
55+
* @returns {string}
56+
*/
57+
function readUntil(until) {
58+
let result = "";
59+
for (;;) {
60+
let char = read(1);
61+
if (char === until) {
62+
break;
63+
} else {
64+
result += char;
65+
}
66+
}
67+
return result;
68+
69+
}
70+
71+
/**
72+
* Read characters from the input that must be equal to `expect`
73+
* @param expect
74+
* @returns {string}
75+
*/
76+
function expect(expect) {
77+
let result = read(expect.length);
78+
if (result !== expect) {
79+
throw "Unexpected input found";
80+
}
81+
return result;
82+
}
83+
84+
/**
85+
* Helper function to handle deserialized arrays.
86+
* @returns {Array}
87+
*/
88+
function handleArray() {
89+
let items = parseInt(readUntil(":"), 10) * 2;
90+
expect("{");
91+
let result = [];
92+
let isKey = true;
93+
let lastItem = null;
94+
for (let idx = 0; idx < items; idx++) {
95+
let item = handleInput();
96+
if (isKey) {
97+
lastItem = item;
98+
isKey = false;
99+
} else {
100+
let numberCheck = lastItem.match(/[0-9]+/);
101+
if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) {
102+
result.push("\"" + lastItem + "\": " + item);
103+
} else {
104+
result.push(lastItem + ": " + item);
105+
}
106+
isKey = true;
107+
}
108+
}
109+
expect("}");
110+
return result;
111+
}
112+
113+
114+
let kind = read(1).toLowerCase();
115+
116+
switch (kind) {
117+
case "n":
118+
expect(";");
119+
return "";
120+
121+
case "i":
122+
case "d":
123+
case "b": {
124+
expect(":");
125+
let data = readUntil(";");
126+
if (kind === "b") {
127+
return (parseInt(data, 10) !== 0);
128+
}
129+
return data;
130+
}
131+
132+
case "a":
133+
expect(":");
134+
return "{" + handleArray() + "}";
135+
136+
case "s": {
137+
expect(":");
138+
let length = readUntil(":");
139+
expect("\"");
140+
let value = read(length);
141+
expect("\";");
142+
if (args[0]) {
143+
return "\"" + value.replace(/"/g, "\\\"") + "\"";
144+
} else {
145+
return "\"" + value + "\"";
146+
}
147+
}
148+
149+
default:
150+
throw "Unknown type: " + kind;
151+
}
152+
}
153+
154+
let inputPart = input.split("");
155+
return handleInput();
156+
}
157+
158+
};
159+
160+
export default PHP;

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import "./tests/operations/Hash.js";
2525
import "./tests/operations/Image.js";
2626
import "./tests/operations/MorseCode.js";
2727
import "./tests/operations/MS.js";
28+
import "./tests/operations/PHP.js";
2829
import "./tests/operations/StrUtils.js";
2930
import "./tests/operations/SeqUtils.js";
3031

test/tests/operations/PHP.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* PHP tests.
3+
*
4+
* @author Jarmo van Lenthe
5+
*
6+
* @copyright Crown Copyright 2017
7+
* @license Apache-2.0
8+
*/
9+
10+
import TestRegister from "../../TestRegister.js";
11+
12+
TestRegister.addTests([
13+
{
14+
name: "PHP Deserialize empty array",
15+
input: "a:0:{}",
16+
expectedOutput: "{}",
17+
recipeConfig: [
18+
{
19+
op: "PHP Deserialize",
20+
args: [true],
21+
},
22+
],
23+
},
24+
{
25+
name: "PHP Deserialize integer",
26+
input: "i:10;",
27+
expectedOutput: "10",
28+
recipeConfig: [
29+
{
30+
op: "PHP Deserialize",
31+
args: [true],
32+
},
33+
],
34+
},
35+
{
36+
name: "PHP Deserialize string",
37+
input: "s:17:\"PHP Serialization\";",
38+
expectedOutput: "\"PHP Serialization\"",
39+
recipeConfig: [
40+
{
41+
op: "PHP Deserialize",
42+
args: [true],
43+
},
44+
],
45+
},
46+
{
47+
name: "PHP Deserialize array (JSON)",
48+
input: "a:2:{s:1:\"a\";i:10;i:0;a:1:{s:2:\"ab\";b:1;}}",
49+
expectedOutput: "{\"a\": 10,\"0\": {\"ab\": true}}",
50+
recipeConfig: [
51+
{
52+
op: "PHP Deserialize",
53+
args: [true],
54+
},
55+
],
56+
},
57+
{
58+
name: "PHP Deserialize array (non-JSON)",
59+
input: "a:2:{s:1:\"a\";i:10;i:0;a:1:{s:2:\"ab\";b:1;}}",
60+
expectedOutput: "{\"a\": 10,0: {\"ab\": true}}",
61+
recipeConfig: [
62+
{
63+
op: "PHP Deserialize",
64+
args: [false],
65+
},
66+
],
67+
},
68+
]);

0 commit comments

Comments
 (0)