added PHP Serialize operation

This commit is contained in:
Brunon Blok 2023-04-06 20:32:50 +00:00
parent 1bc88728f0
commit bf1e708a4c
2 changed files with 256 additions and 0 deletions

View file

@ -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.<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>";
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;

View file

@ -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: []
}
]
}
]);