Skip to content

Commit 7c8be12

Browse files
authored
Merge pull request #1548 from brun0ne/add-php-serialization
Add new operation: PHP Serialize
2 parents e849569 + a40aed2 commit 7c8be12

File tree

4 files changed

+240
-0
lines changed

4 files changed

+240
-0
lines changed

src/core/config/Categories.json

+1
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@
470470
"Jq",
471471
"CSS selector",
472472
"PHP Deserialize",
473+
"PHP Serialize",
473474
"Microsoft Script Decoder",
474475
"Strip HTML tags",
475476
"Diff",

src/core/operations/PHPSerialize.mjs

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* @author brun0ne [[email protected]]
3+
* @copyright Crown Copyright 2023
4+
* @license Apache-2.0
5+
*/
6+
7+
import Operation from "../Operation.mjs";
8+
import OperationError from "../errors/OperationError.mjs";
9+
10+
/**
11+
* PHP Serialize operation
12+
*/
13+
class PHPSerialize extends Operation {
14+
15+
/**
16+
* PHPSerialize constructor
17+
*/
18+
constructor() {
19+
super();
20+
21+
this.name = "PHP Serialize";
22+
this.module = "Default";
23+
this.description = "Performs PHP serialization on JSON data.<br><br>This function does not support <code>object</code> tags.<br><br>Since PHP doesn't distinguish dicts and arrays, this operation is not always symmetric to <code>PHP Deserialize</code>.<br><br>Example:<br><code>[5,&quot;abc&quot;,true]</code><br>becomes<br><code>a:3:{i:0;i:5;i:1;s:3:&quot;abc&quot;;i:2;b:1;}<code>";
24+
this.infoURL = "https://www.phpinternalsbook.com/php5/classes_objects/serialization.html";
25+
this.inputType = "JSON";
26+
this.outputType = "string";
27+
this.args = [];
28+
}
29+
30+
/**
31+
* @param {JSON} input
32+
* @param {Object[]} args
33+
* @returns {string}
34+
*/
35+
run(input, args) {
36+
/**
37+
* Determines if a number is an integer
38+
* @param {number} value
39+
* @returns {boolean}
40+
*/
41+
function isInteger(value) {
42+
return typeof value === "number" && parseInt(value.toString(), 10) === value;
43+
}
44+
45+
/**
46+
* Serialize basic types
47+
* @param {string | number | boolean} content
48+
* @returns {string}
49+
*/
50+
function serializeBasicTypes(content) {
51+
const basicTypes = {
52+
"string": "s",
53+
"integer": "i",
54+
"float": "d",
55+
"boolean": "b"
56+
};
57+
/**
58+
* Booleans
59+
* cast to 0 or 1
60+
*/
61+
if (typeof content === "boolean") {
62+
return `${basicTypes.boolean}:${content ? 1 : 0}`;
63+
}
64+
/* Numbers */
65+
if (typeof content === "number") {
66+
if (isInteger(content)) {
67+
return `${basicTypes.integer}:${content.toString()}`;
68+
} else {
69+
return `${basicTypes.float}:${content.toString()}`;
70+
}
71+
}
72+
/* Strings */
73+
if (typeof content === "string")
74+
return `${basicTypes.string}:${content.length}:"${content}"`;
75+
76+
/** This should be unreachable */
77+
throw new OperationError(`Encountered a non-implemented type: ${typeof content}`);
78+
}
79+
80+
/**
81+
* Recursively serialize
82+
* @param {*} object
83+
* @returns {string}
84+
*/
85+
function serialize(object) {
86+
/* Null */
87+
if (object == null) {
88+
return `N;`;
89+
}
90+
91+
if (typeof object !== "object") {
92+
/* Basic types */
93+
return `${serializeBasicTypes(object)};`;
94+
} else if (object instanceof Array) {
95+
/* Arrays */
96+
const serializedElements = [];
97+
98+
for (let i = 0; i < object.length; i++) {
99+
serializedElements.push(`${serialize(i)}${serialize(object[i])}`);
100+
}
101+
102+
return `a:${object.length}:{${serializedElements.join("")}}`;
103+
} else if (object instanceof Object) {
104+
/**
105+
* Objects
106+
* Note: the output cannot be guaranteed to be in the same order as the input
107+
*/
108+
const serializedElements = [];
109+
const keys = Object.keys(object);
110+
111+
for (const key of keys) {
112+
serializedElements.push(`${serialize(key)}${serialize(object[key])}`);
113+
}
114+
115+
return `a:${keys.length}:{${serializedElements.join("")}}`;
116+
}
117+
118+
/** This should be unreachable */
119+
throw new OperationError(`Encountered a non-implemented type: ${typeof object}`);
120+
}
121+
122+
return serialize(input);
123+
}
124+
}
125+
126+
export default PHPSerialize;

tests/operations/index.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ import "./tests/ParseUDP.mjs";
126126
import "./tests/PEMtoHex.mjs";
127127
import "./tests/PGP.mjs";
128128
import "./tests/PHP.mjs";
129+
import "./tests/PHPSerialize.mjs";
129130
import "./tests/PowerSet.mjs";
130131
import "./tests/Protobuf.mjs";
131132
import "./tests/PubKeyFromCert.mjs";
+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* PHP Serialization tests.
3+
*
4+
* @author brun0ne [[email protected]]
5+
*
6+
* @copyright Crown Copyright 2023
7+
* @license Apache-2.0
8+
*/
9+
10+
import TestRegister from "../../lib/TestRegister.mjs";
11+
12+
TestRegister.addTests([
13+
{
14+
name: "PHP Serialize empty array",
15+
input: "[]",
16+
expectedOutput: "a:0:{}",
17+
recipeConfig: [
18+
{
19+
op: "PHP Serialize",
20+
args: []
21+
}
22+
]
23+
},
24+
{
25+
name: "PHP Serialize empty object",
26+
input: "{}",
27+
expectedOutput: "a:0:{}",
28+
recipeConfig: [
29+
{
30+
op: "PHP Serialize",
31+
args: []
32+
}
33+
]
34+
},
35+
{
36+
name: "PHP Serialize null",
37+
input: "null",
38+
expectedOutput: "N;",
39+
recipeConfig: [
40+
{
41+
op: "PHP Serialize",
42+
args: []
43+
}
44+
]
45+
},
46+
{
47+
name: "PHP Serialize integer",
48+
input: "10",
49+
expectedOutput: "i:10;",
50+
recipeConfig: [
51+
{
52+
op: "PHP Serialize",
53+
args: []
54+
}
55+
]
56+
},
57+
{
58+
name: "PHP Serialize float",
59+
input: "14.523",
60+
expectedOutput: "d:14.523;",
61+
recipeConfig: [
62+
{
63+
op: "PHP Serialize",
64+
args: []
65+
}
66+
]
67+
},
68+
{
69+
name: "PHP Serialize boolean",
70+
input: "[true, false]",
71+
expectedOutput: "a:2:{i:0;b:1;i:1;b:0;}",
72+
recipeConfig: [
73+
{
74+
op: "PHP Serialize",
75+
args: []
76+
}
77+
]
78+
},
79+
{
80+
name: "PHP Serialize string",
81+
input: "\"Test string to serialize\"",
82+
expectedOutput: "s:24:\"Test string to serialize\";",
83+
recipeConfig: [
84+
{
85+
op: "PHP Serialize",
86+
args: []
87+
}
88+
]
89+
},
90+
{
91+
name: "PHP Serialize object",
92+
input: "{\"a\": 10,\"0\": {\"ab\": true}}",
93+
expectedOutput: "a:2:{s:1:\"0\";a:1:{s:2:\"ab\";b:1;}s:1:\"a\";i:10;}",
94+
recipeConfig: [
95+
{
96+
op: "PHP Serialize",
97+
args: []
98+
}
99+
]
100+
},
101+
{
102+
name: "PHP Serialize array",
103+
input: "[1,\"abc\",true,{\"x\":1,\"y\":2}]",
104+
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;}}",
105+
recipeConfig: [
106+
{
107+
op: "PHP Serialize",
108+
args: []
109+
}
110+
]
111+
}
112+
]);

0 commit comments

Comments
 (0)