Last active
September 24, 2024 08:49
-
-
Save storeman/cecb10bab249f43562cddabc1d9dd7c9 to your computer and use it in GitHub Desktop.
PHP unserialize in Postgresql to json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Decode a php serialized value to json. This function only supports basic | |
data types: | |
- arrays (will always become a json object) | |
- booleans | |
- integers | |
- floats | |
- strings | |
- NULL | |
The php_unserialize(text) function is a helper function which extracts the first value | |
found in the string and returns a ROW( vartype, varval, serializedlength) | |
The php_unserialize_to_json(text) function returns the json value extracted from | |
the serialized string. | |
*/ | |
--- | |
--- This function is the helper function | |
--- | |
CREATE OR REPLACE FUNCTION public.php_unserialize(str text) | |
RETURNS json AS | |
$BODY$ | |
DECLARE | |
vartype CHAR; | |
varlength INT; | |
jsonstr TEXT; | |
varcount INT; | |
jsonval JSONB; | |
arrkey JSON; | |
arrval JSON; | |
-- String length of the serialized data | |
serialized_string_length INT; | |
BEGIN | |
CASE substring(str, 1, 1) | |
WHEN 'a' THEN -- array | |
-- init object | |
jsonval := '{}'::jsonb; | |
-- remove the "a" and ":" characters | |
str := substring(str, 3); | |
-- Detect number of values in array | |
varlength := substring(str, 1, position(':' IN str) - 1)::INT; | |
-- Base size of array is 5 (a:[size]:{}) | |
serialized_string_length := 5 + char_length(varlength::TEXT); | |
-- If no values, return empty object, as this always returns objects | |
IF varlength = 0 THEN | |
return json_build_array('array', '{}'::JSON, serialized_string_length)::JSON; | |
END IF; | |
-- remove the array size and ":{" | |
str := substring(str, char_length(varlength::TEXT) + 3); | |
-- Find the number of variables specified | |
FOR varcount IN 1 .. varlength LOOP | |
-- Find the value of the key and remove it from base string | |
arrkey := php_unserialize(str); | |
str := substring(str, (arrkey->>2)::INT + 1); | |
-- Find the value of the value and remove it from base string | |
arrval := php_unserialize(str); | |
str := substring(str, (arrval->>2)::INT + 1); | |
serialized_string_length := serialized_string_length + (arrkey->>2)::INT + (arrval->>2)::INT; | |
-- Append value | |
jsonval := jsonval || jsonb_build_object(arrkey->>1, arrval->1); | |
END LOOP; | |
return json_build_array('array', jsonval, serialized_string_length); | |
WHEN 'b' THEN -- boolean | |
return json_build_array('bool',(CASE substring(str, 3, 1) WHEN '1' THEN TRUE ELSE FALSE END)::BOOL, 4); | |
WHEN 'd' THEN -- float | |
return json_build_array('float', substring(str, 3, position(';' IN str) - 3)::DOUBLE PRECISION, position(';' IN str)); | |
WHEN 'i' THEN -- int | |
return json_build_array('int', substring(str, 3, position(';' IN str) - 3)::INT, position(';' IN str)); | |
WHEN 'N' THEN -- null | |
return json_build_array('null', NULL, 2); | |
WHEN 's' THEN -- string | |
varlength := regexp_replace(substring(str, 3, position(':' IN str)), '[^0-9]+', '', 'g')::INT; | |
return json_build_array('string', substring(str, char_length(varlength::TEXT) + 5, varlength)::TEXT, position(';' IN str)); | |
ELSE | |
RAISE EXCEPTION 'Unable to decode serialized value, unsupported type: %', substr(str, 1, 1); | |
END CASE; | |
END; | |
$BODY$ | |
LANGUAGE plpgsql IMMUTABLE | |
COST 10; | |
--- | |
--- The main function | |
--- | |
CREATE OR REPLACE FUNCTION public.php_unserialize_to_json(str text) | |
RETURNS json AS | |
$BODY$ | |
DECLARE | |
varlength INT; | |
BEGIN | |
CASE substring(str, 1, 1) | |
WHEN 'a' THEN | |
return php_unserialize(str)->1; | |
WHEN 'b' THEN | |
return php_unserialize(str)->1; | |
WHEN 'd' THEN | |
return php_unserialize(str)->1; | |
WHEN 'i' THEN | |
return php_unserialize(str)->1; | |
WHEN 'N' THEN | |
return php_unserialize(str)->1; | |
WHEN 's' THEN | |
return php_unserialize(str)->1; | |
ELSE | |
RETURN NULL; | |
END CASE; | |
END; | |
$BODY$ | |
LANGUAGE plpgsql IMMUTABLE | |
COST 10; | |
CREATE OR REPLACE FUNCTION public.test_php_unserialize_to_json() | |
RETURNS void AS | |
$BODY$ | |
DECLARE | |
BEGIN | |
IF | |
php_unserialize_to_json('N;')::TEXT | |
<> 'null' | |
THEN | |
RAISE EXCEPTION 'Expected null, got "%"', php_unserialize_to_json('N;')::TEXT; | |
END IF; | |
IF | |
php_unserialize_to_json('b:1;')::TEXT | |
<> 'true' | |
THEN | |
RAISE EXCEPTION 'Expected true, got "%"', php_unserialize_to_json('b:1;')::TEXT; | |
END IF; | |
IF | |
php_unserialize_to_json('b:0;')::TEXT | |
<> 'false' | |
THEN | |
RAISE EXCEPTION 'Expected false, got "%"', php_unserialize_to_json('b:0;')::TEXT; | |
END IF; | |
IF | |
php_unserialize_to_json('i:1337;')::TEXT | |
<> '1337' | |
THEN | |
RAISE EXCEPTION 'An integer 1337, got "%"', php_unserialize_to_json('i:1337;')::TEXT; | |
END IF; | |
IF | |
php_unserialize_to_json('d:1.337;')::TEXT | |
<> '1.337' | |
THEN | |
RAISE EXCEPTION 'An double 1.337, got "%"', php_unserialize_to_json('d:1.337;')::TEXT; | |
END IF; | |
IF | |
php_unserialize_to_json('s:0:"";')::TEXT | |
<> '""' | |
THEN | |
RAISE EXCEPTION 'An empty string, got "%"', php_unserialize_to_json('s:0:"";')::TEXT; | |
END IF; | |
IF | |
php_unserialize_to_json('s:8:"a string";')::TEXT | |
<> '"a string"' | |
THEN | |
RAISE EXCEPTION 'Expected value "a string", got "%"', php_unserialize_to_json('s:8:"a string";')::TEXT; | |
END IF; | |
IF | |
(php_unserialize_to_json('s:15:"a longer string";'))::TEXT | |
<> '"a longer string"' | |
THEN | |
RAISE EXCEPTION 'Expected value "a longer string", got "%"', php_unserialize_to_json('s:15:"a longer string";')::TEXT; | |
END IF; | |
IF | |
(php_unserialize_to_json('a:0:{}'))::TEXT | |
<> '{}' | |
THEN | |
RAISE EXCEPTION 'Expected empty array, got "%"', php_unserialize_to_json('a:0:{}')::TEXT; | |
END IF; | |
IF | |
(php_unserialize_to_json('a:1:{i:0;s:5:"array";}'))::TEXT | |
<> ('{"0": "array"}'::JSON)::TEXT | |
THEN | |
RAISE EXCEPTION 'Expected array with one value, got "%"', php_unserialize_to_json('a:1:{i:0;s:5:"array";}')::TEXT; | |
END IF; | |
IF | |
(php_unserialize_to_json('a:2:{i:0;i:123;i:1;b:1;}'))::TEXT | |
<> ('{"0": 123, "1": true}'::JSON)::TEXT | |
THEN | |
RAISE EXCEPTION 'Expected array with one value, got "%"', php_unserialize_to_json('a:2:{i:0;i:123;i:1;b:1;}')::TEXT; | |
END IF; | |
IF | |
(php_unserialize_to_json('a:1:{i:0;i:123;}'))::TEXT | |
<> ('{"0": 123}'::JSON)::TEXT | |
THEN | |
RAISE EXCEPTION 'Expected array with one value, got "%"', php_unserialize_to_json('a:1:{i:0;i:123;}')::TEXT; | |
END IF; | |
IF | |
php_unserialize_to_json('a:5:{i:0;d:1;s:1:"k";a:3:{i:0;s:1:"a";i:1;s:1:"b";s:3:"sub";a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}}i:1;N;i:2;b:1;i:3;b:0;}')::TEXT | |
<> ('{"0": 1, "1": null, "2": true, "3": false, "k": {"0": "a", "1": "b", "sub": {"0": "a", "1": "b", "2": "c"}}}'::JSON)::TEXT | |
THEN | |
RAISE EXCEPTION 'Unexpected complex value, got: "%"', php_unserialize_to_json('a:5:{i:0;d:1;s:1:"k";a:3:{i:0;s:1:"a";i:1;s:1:"b";s:3:"sub";a:3:{i:0;s:1:"a";i:1;s:1:"b";i:2;s:1:"c";}}i:1;N;i:2;b:1;i:3;b:0;}')::TEXT; | |
END IF; | |
-- SELECT php_unserialize_to_json('a:2:{i:0;d:1;s:1:"k";a:2:{i:0;s:1:"a";i:1;s:1:"b";}}')::TEXT; | |
END; | |
$BODY$ | |
LANGUAGE plpgsql IMMUTABLE | |
COST 10; | |
SELECT public.test_php_unserialize_to_json(); | |
DROP FUNCTION public.test_php_unserialize_to_json(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I modded your gist for support Objects (stdClass) and strings separated by ";" ("text1;text2;text3;text4")