From ff8f0463f404a185199cb38e540f8b8ec877de1d Mon Sep 17 00:00:00 2001 From: Sorin Sarca Date: Tue, 24 Dec 2024 22:07:53 +0200 Subject: [PATCH] Easier migration from v3 --- data.txt | 1 - src/Serializer.php | 68 ++++++++++++++++++++++++++++++++++-- tests/PHP80/V3CompatTest.php | 2 +- 3 files changed, 67 insertions(+), 4 deletions(-) delete mode 100644 data.txt diff --git a/data.txt b/data.txt deleted file mode 100644 index 5591288..0000000 --- a/data.txt +++ /dev/null @@ -1 +0,0 @@ -C:32:"Opis\Closure\SerializableClosure":181:{@RQlO9jkdxEek6V0Q5LV32kwuyRaPK7EhiHQjSKhTgks=.a:5:{s:3:"use";a:0:{}s:8:"function";s:12:"fn() => "ok"";s:5:"scope";N;s:4:"this";N;s:4:"self";s:32:"00000000000000040000000000000000";}} \ No newline at end of file diff --git a/src/Serializer.php b/src/Serializer.php index 0040bd9..eaf2b6a 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -131,9 +131,47 @@ public static function serialize(mixed $data, ?SecurityProviderInterface $securi public static function unserialize(string $data, ?SecurityProviderInterface $security = null): mixed { self::$init || self::init(); - return (new DeserializationHandler())->unserialize(self::decode($data, $security)); + + $skipDecode = false; + // when $data starts with @ - it indicates that it is v4 signed + if (self::$v3Compatible && ($data[0] ?? null) !== "@") { + // in v3 only the content of SerializableClosure is signed + // we must use some simple heuristics to determine if this is v3 + // this will only work if the serialized data contains a closure + // the security checks will be made inside SerializableClosure::unserialize() + $skipDecode = str_contains($data, 'C:32:"Opis\Closure\SerializableClosure"'); + } + + // Create a new deserialization handler + $handler = new DeserializationHandler(); + + if (!$skipDecode) { + // current - v4 + return $handler->unserialize(self::decode($data, $security)); + } + + // v3 + if (!$security || self::$securityProvider === $security) { + return $handler->unserialize($data); + } + + // we have to use the current security provider + $prevSecurity = self::$securityProvider; + self::$securityProvider = $security; + + try { + return $handler->unserialize($data); + } finally { + self::$securityProvider = $prevSecurity; + } } + /** + * Unserialize data from v3.x using a security provider (optional) + * DO NOT use this to unserialize data from v4 + * This method was created in order to help with migration from v3 to v4 + * @throws SecurityException + */ public static function unserialize_v3(string $data, ?SecurityProviderInterface $security = null): mixed { self::$init || self::init(); @@ -154,6 +192,9 @@ public static function unserialize_v3(string $data, ?SecurityProviderInterface $ } } + /** + * Sign data using a security provider + */ public static function encode(string $data, ?SecurityProviderInterface $security = null): string { $security ??= self::$securityProvider; @@ -164,6 +205,7 @@ public static function encode(string $data, ?SecurityProviderInterface $security } /** + * Extract signed data using a security provider * @throws SecurityException */ public static function decode(string $data, ?SecurityProviderInterface $security = null): string @@ -223,6 +265,12 @@ public static function classInfo(string $class): object ]; } + /** + * Prevent serialization boxing for specified classes + * @param string ...$class + * @return void + * @throws \ReflectionException + */ public static function preventBoxing(string ...$class): void { foreach ($class as $cls) { @@ -240,6 +288,9 @@ public static function getUnserializer(string $class): ?callable return self::classInfo($class)->unserialize ?? null; } + /** + * Use a generic object serializer/deserializer for specified classes + */ public static function setObjectSerialization(string ...$class): void { $serialize = [CustomSplSerialization::class, "sObject"]; @@ -255,6 +306,9 @@ public static function setObjectSerialization(string ...$class): void } } + /** + * Use custom serialization/deserialization for a class + */ public static function setCustomSerialization(string $class, ?callable $serialize, ?callable $unserialize): void { $data = self::classInfo($class); @@ -262,6 +316,9 @@ public static function setCustomSerialization(string $class, ?callable $serializ $data->unserialize = $unserialize; } + /** + * Set current security provider + */ public static function setSecurityProvider(SecurityProviderInterface|null|string $security): void { if (is_string($security)) { @@ -270,12 +327,19 @@ public static function setSecurityProvider(SecurityProviderInterface|null|string self::$securityProvider = $security; } + /** + * Get current security provider + */ public static function getSecurityProvider(): ?SecurityProviderInterface { return self::$securityProvider; } - public static function isEnum($value): bool + /** + * Helper function to detect if a value is Enum + * @internal + */ + public static function isEnum(mixed $value): bool { return self::$enumExists && ($value instanceof UnitEnum); } diff --git a/tests/PHP80/V3CompatTest.php b/tests/PHP80/V3CompatTest.php index 25c65c2..bdc5d23 100644 --- a/tests/PHP80/V3CompatTest.php +++ b/tests/PHP80/V3CompatTest.php @@ -51,6 +51,6 @@ private function u(string $name, SecurityProviderInterface|string|null $security if (is_string($security)) { $security = new DefaultSecurityProvider($security); } - return Serializer::unserialize_v3($data, $security); + return Serializer::unserialize($data, $security); } } \ No newline at end of file