Skip to content

Commit 044fe97

Browse files
committed
First version
0 parents  commit 044fe97

11 files changed

+6617
-0
lines changed

Diff for: .editorconfig

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
root = true
2+
3+
[*]
4+
indent_style = tab
5+
indent_size = 4
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[*.md]
12+
indent_style = space
13+
indent_size = 2
14+
trim_trailing_whitespace = false
15+
insert_final_newline = false

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
pkg

Diff for: jest.config.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
globals: {
5+
'ts-jest': {
6+
warnOnly: true,
7+
isolatedModules: true,
8+
},
9+
},
10+
};

Diff for: package.json

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "php-serialized-data-js",
3+
"version": "1.0.0",
4+
"repository": "https://github.com/LachlanArthur/php-serialized-data-js",
5+
"author": "LachlanArthur",
6+
"license": "MIT",
7+
"scripts": {
8+
"publish-pkg": "pika publish --no-tests",
9+
"test": "jest",
10+
"build": "pika build",
11+
"version": "npm run build"
12+
},
13+
"@pika/pack": {
14+
"pipeline": [
15+
[
16+
"@pika/plugin-ts-standard-pkg"
17+
],
18+
[
19+
"@pika/plugin-build-node"
20+
],
21+
[
22+
"@pika/plugin-build-web"
23+
]
24+
]
25+
},
26+
"devDependencies": {
27+
"@pika/pack": "^0.5.0",
28+
"@pika/plugin-build-node": "^0.9.2",
29+
"@pika/plugin-build-web": "^0.9.2",
30+
"@pika/plugin-ts-standard-pkg": "^0.9.2",
31+
"@types/jest": "^25.2.1",
32+
"jest": "^25.4.0",
33+
"ts-jest": "^25.4.0",
34+
"typescript": "^3.8.3"
35+
},
36+
"dependencies": {}
37+
}

Diff for: readme.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# php-serialized-data
2+
3+
Parse PHP serialized data with JS
4+
5+
PHP's `serialize` function doesn't have a spec, so I used the handy [Kaitai Struct spec](https://formats.kaitai.io/php_serialized_value/index.html) as reference instead
6+
7+
## Usage
8+
9+
```js
10+
import { parse } from 'php-serialized-data';
11+
12+
const data = parse( 'O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}' );
13+
14+
/*
15+
ObjectToken(
16+
className: 'stdClass'
17+
value: Map [
18+
[ StringToken( 'foo' ), StringToken( 'bar' ) ],
19+
]
20+
)
21+
*/
22+
```
23+
24+
It even works with multi-byte data like emoji:
25+
26+
```js
27+
import { parse } from 'php-serialized-data';
28+
29+
const data = parse( 's:4:"🐊";' );
30+
31+
/*
32+
StringToken( '🐊' )
33+
*/
34+
```
35+
36+
## Supports
37+
38+
- Null
39+
- Integer
40+
- Float
41+
- Infinity
42+
- NaN
43+
- Scientific notation
44+
- String
45+
- Multi-byte (e.g. emoji)
46+
- Boolean
47+
- Array
48+
- Object
49+
- Classes
50+
- Custom Objects (contain arbitrary serialized data. e.g. `SplDoublyLinkedList`)
51+
- Object Reference
52+
- Value Reference
53+
54+
## TODO
55+
56+
- Dereference object & value references
57+
- Also circular references
58+
- Output conversion to plain JS types
59+
- Nulls in private/protected class property names
60+
- Throw on trailing data

Diff for: src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { PHPTypes, parse } from './parse';

Diff for: src/parse.spec.ts

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { parse, PHPTypes } from './parse';
2+
3+
describe( 'Parser', () => {
4+
5+
test( 'CustomObject Empty', () => {
6+
const [ token, increment ] = parse( 'C:9:"ClassName":0:{}' );
7+
expect( token ).toEqual( new PHPTypes.PHPCustomObject( '', 'ClassName' ) );
8+
expect( increment ).toBe( 20 );
9+
} );
10+
11+
test( 'CustomObject Simple', () => {
12+
const [ token, increment ] = parse( 'C:9:"ClassName":15:{serialized data}' );
13+
expect( token ).toEqual( new PHPTypes.PHPCustomObject( 'serialized data', 'ClassName' ) );
14+
expect( increment ).toBe( 36 );
15+
} );
16+
17+
test( 'Null', () => {
18+
const [ token, increment ] = parse( 'N;' );
19+
expect( token ).toEqual( new PHPTypes.PHPNull() );
20+
expect( increment ).toBe( 2 );
21+
} );
22+
23+
test( 'Object Empty', () => {
24+
const [ token, increment ] = parse( 'O:8:"stdClass":0:{}' );
25+
expect( token ).toEqual( new PHPTypes.PHPObject( new Map(), 'stdClass' ) );
26+
expect( increment ).toBe( 19 );
27+
} );
28+
29+
test( 'Object Simple', () => {
30+
const [ token, increment ] = parse( 'O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}' );
31+
expect( token ).toEqual( new PHPTypes.PHPObject(
32+
new Map( [
33+
[ new PHPTypes.PHPString( 'foo' ), new PHPTypes.PHPString( 'bar' ) ]
34+
] ),
35+
'stdClass'
36+
) );
37+
expect( increment ).toBe( 39 );
38+
} );
39+
40+
test( 'Object Nested', () => {
41+
const [ token, increment ] = parse( 'O:8:"stdClass":2:{s:1:"0";O:8:"stdClass":1:{s:1:"0";s:3:"foo";}s:1:"1";O:8:"stdClass":1:{s:1:"0";s:3:"bar";}}' );
42+
expect( token ).toEqual( new PHPTypes.PHPObject(
43+
new Map( [
44+
[ new PHPTypes.PHPString( '0' ), new PHPTypes.PHPObject(
45+
new Map( [ [ new PHPTypes.PHPString( '0' ), new PHPTypes.PHPString( 'foo' ) ] ] ),
46+
'stdClass'
47+
) ],
48+
[ new PHPTypes.PHPString( '1' ), new PHPTypes.PHPObject(
49+
new Map( [ [ new PHPTypes.PHPString( '0' ), new PHPTypes.PHPString( 'bar' ) ] ] ),
50+
'stdClass'
51+
) ],
52+
] ),
53+
'stdClass'
54+
) );
55+
expect( increment ).toBe( 109 );
56+
} );
57+
58+
test( 'Reference', () => {
59+
const [ token, increment ] = parse( 'R:1;' );
60+
expect( token ).toEqual( new PHPTypes.PHPReference( 1 ) );
61+
expect( increment ).toBe( 4 );
62+
} );
63+
64+
test( 'String Empty', () => {
65+
const [ token, increment ] = parse( 's:0:"";' );
66+
expect( token ).toEqual( new PHPTypes.PHPString( '' ) );
67+
expect( increment ).toBe( 7 );
68+
} );
69+
70+
test( 'String Complicated', () => {
71+
const [ token, increment ] = parse( 's:103:"This text - "s:38:"She exclaimed "Hello?" into the phone.";" - is an example of a serialized PHP value.";' );
72+
expect( token ).toEqual( new PHPTypes.PHPString( 'This text - "s:38:"She exclaimed "Hello?" into the phone.";" - is an example of a serialized PHP value.' ) );
73+
expect( increment ).toBe( 112 );
74+
} );
75+
76+
test( 'String Emoji', () => {
77+
const [ token, increment ] = parse( 's:4:"🐊";' );
78+
expect( token ).toEqual( new PHPTypes.PHPString( '🐊' ) );
79+
expect( increment ).toBe( 9 );
80+
} );
81+
82+
test( 'Array Empty', () => {
83+
const [ token, increment ] = parse( 'a:0:{}' );
84+
expect( token ).toEqual( new PHPTypes.PHPArray( new Map( [] ) ) );
85+
expect( increment ).toBe( 6 );
86+
} );
87+
88+
test( 'Array Simple', () => {
89+
const [ token, increment ] = parse( 'a:2:{i:0;s:3:"foo";i:1;s:3:"bar";}' );
90+
expect( token ).toEqual( new PHPTypes.PHPArray( new Map( [
91+
[ new PHPTypes.PHPInteger( 0 ), new PHPTypes.PHPString( 'foo' ) ],
92+
[ new PHPTypes.PHPInteger( 1 ), new PHPTypes.PHPString( 'bar' ) ],
93+
] ) ) );
94+
expect( increment ).toBe( 34 );
95+
} );
96+
97+
test( 'Array Nested', () => {
98+
const [ token, increment ] = parse( 'a:2:{i:0;a:1:{i:0;s:3:"foo";}i:1;a:1:{i:0;s:3:"bar";}}' );
99+
expect( token ).toEqual( new PHPTypes.PHPArray( new Map( [
100+
[ new PHPTypes.PHPInteger( 0 ), new PHPTypes.PHPArray( new Map( [
101+
[ new PHPTypes.PHPInteger( 0 ), new PHPTypes.PHPString( 'foo' ) ],
102+
] ) ) ],
103+
[ new PHPTypes.PHPInteger( 1 ), new PHPTypes.PHPArray( new Map( [
104+
[ new PHPTypes.PHPInteger( 0 ), new PHPTypes.PHPString( 'bar' ) ],
105+
] ) ) ],
106+
] ) ) );
107+
expect( increment ).toBe( 54 );
108+
} );
109+
110+
test( 'Boolean False', () => {
111+
const [ token, increment ] = parse( 'b:0;' );
112+
expect( token ).toEqual( new PHPTypes.PHPBoolean( false ) );
113+
expect( increment ).toBe( 4 );
114+
} );
115+
116+
test( 'Boolean True', () => {
117+
const [ token, increment ] = parse( 'b:1;' );
118+
expect( token ).toEqual( new PHPTypes.PHPBoolean( true ) );
119+
expect( increment ).toBe( 4 );
120+
} );
121+
122+
test( 'Float Positive', () => {
123+
const [ token, increment ] = parse( 'd:12.34;' );
124+
expect( token ).toBeInstanceOf( PHPTypes.PHPFloat );
125+
expect( token.value ).toBeCloseTo( 12.34 );
126+
expect( increment ).toBe( 8 );
127+
} );
128+
129+
test( 'Float Negative', () => {
130+
const [ token, increment ] = parse( 'd:-12.34;' );
131+
expect( token ).toBeInstanceOf( PHPTypes.PHPFloat );
132+
expect( token.value ).toBeCloseTo( -12.34 );
133+
expect( increment ).toBe( 9 );
134+
} );
135+
136+
test( 'Float Scientific Notation Positive', () => {
137+
const [ token, increment ] = parse( 'd:1.1111111111111112E+69;' );
138+
expect( token ).toBeInstanceOf( PHPTypes.PHPFloat );
139+
expect( token.value ).toBeCloseTo( 1.1111111111111112e+69 );
140+
expect( increment ).toBe( 25 );
141+
} );
142+
143+
test( 'Float Scientific Notation Negative', () => {
144+
const [ token, increment ] = parse( 'd:-1.1111111111111112E+69;' );
145+
expect( token ).toBeInstanceOf( PHPTypes.PHPFloat );
146+
expect( token.value ).toBeCloseTo( -1.1111111111111112e+69 );
147+
expect( increment ).toBe( 26 );
148+
} );
149+
150+
test( 'Float Infinity Positive', () => {
151+
const [ token, increment ] = parse( 'd:INF;' );
152+
expect( token ).toBeInstanceOf( PHPTypes.PHPFloat );
153+
expect( token.value ).toBe( Infinity );
154+
expect( increment ).toBe( 6 );
155+
} );
156+
157+
test( 'Float Infinity Negative', () => {
158+
const [ token, increment ] = parse( 'd:-INF;' );
159+
expect( token ).toBeInstanceOf( PHPTypes.PHPFloat );
160+
expect( token.value ).toBe( -Infinity );
161+
expect( increment ).toBe( 7 );
162+
} );
163+
164+
test( 'Float NaN', () => {
165+
const [ token, increment ] = parse( 'd:NAN;' );
166+
expect( token ).toBeInstanceOf( PHPTypes.PHPFloat );
167+
expect( token.value ).toBeNaN();
168+
expect( increment ).toBe( 6 );
169+
} );
170+
171+
test( 'Integer Positive', () => {
172+
const [ token, increment ] = parse( 'i:1234;' );
173+
expect( token.value ).toBe( 1234 );
174+
expect( increment ).toBe( 7 );
175+
} );
176+
177+
test( 'Integer Negative', () => {
178+
const [ token, increment ] = parse( 'i:-1234;' );
179+
expect( token.value ).toBe( -1234 );
180+
expect( increment ).toBe( 8 );
181+
} );
182+
183+
} );

0 commit comments

Comments
 (0)