, * composerNameToPackageKeyMap: array, * packageObjects: string, * packageClasses: list, * } */ class PackageCacheEntry { /** * Package configuration. Used by the PackageManager to identify "active" packages. * Every key in this array represents an active extension. * * @var StatesConfiguration */ private array $configuration; /** * Alternative names for packages mapping to the package key. * Typically filled from replace section in composer.json * * @var array */ private array $aliasMap; /** * Map from composer name of a package (key in this array) to its package key (value) * * @var array */ private array $composerNameMap; /** * @var array */ private array $packages; /** * Identifier for the current state, which can optionally * stored in the cache entry or artifact. * Currently, only used in Composer mode, where the identifier * is comprised from the composer.lock file and stored alongside the artifact. */ private ?string $identifier = null; /** * @param StatesConfiguration $configuration * @param array $aliasMap * @param array $composerNameMap * @param array $packages */ private function __construct( array $configuration, array $aliasMap, array $composerNameMap, array $packages ) { $this->configuration = $configuration; $this->aliasMap = $aliasMap; $this->composerNameMap = $composerNameMap; $this->packages = $packages; } /** * Validates whether the configuration has the correct version * * @throws PackageStatesUnavailableException */ public static function ensureValidPackageConfiguration(array $configuration): void { if (($configuration['version'] ?? 0) < 5) { throw new PackageStatesUnavailableException('The PackageStates.php file is either corrupt or unavailable.', 1381507733); } } /** * @param StatesConfiguration $packageStatesConfiguration * @param array $packageAliasMap * @param array $composerNameToPackageKeyMap * @param array $packageObjects */ public static function fromPackageData( array $packageStatesConfiguration, array $packageAliasMap, array $composerNameToPackageKeyMap, array $packageObjects ): self { self::ensureValidPackageConfiguration($packageStatesConfiguration); return new self( $packageStatesConfiguration, $packageAliasMap, $composerNameToPackageKeyMap, $packageObjects ); } /** * @param CacheArray $packageData */ public static function fromCache(array $packageData): self { try { self::ensureValidPackageConfiguration($packageData['packageStatesConfiguration'] ?? []); } catch (PackageStatesUnavailableException $e) { // Invalidate the cache entry throw new PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883341, $e); } // Package objects can now contain classes from userland. // We nevertheless restrict the unserialize call here to classes, // that have been identified during caching. // Yes, this list of classes could be tainted, but then, why not taint, // the complete cache file directly? A tainted list of class names isn't // really less obvious than other PHP code in the cache file. $cacheEntry = new self( $packageData['packageStatesConfiguration'], $packageData['packageAliasMap'], $packageData['composerNameToPackageKeyMap'], unserialize( $packageData['packageObjects'], // data is read from `PackageStates.php` already, if that file is tainted for // deserialization, code execution would have been possible anyway already ['allowed_classes' => $packageData['packageClasses']], ), ); $cacheEntry->identifier = $packageData['identifier'] ?? null; return $cacheEntry; } public function serialize(): string { $serializedPackages = serialize($this->packages); $deserializationService = new DeserializationService(); return var_export( [ 'identifier' => $this->identifier, 'packageStatesConfiguration' => $this->configuration, 'packageAliasMap' => $this->aliasMap, 'composerNameToPackageKeyMap' => $this->composerNameMap, 'packageObjects' => $serializedPackages, 'packageClasses' => $deserializationService->parseClassNames($serializedPackages), ], true ); } public function getIdentifier(): ?string { return $this->identifier; } public function withIdentifier(string $identifier): self { $newEntry = clone $this; $newEntry->identifier = $identifier; return $newEntry; } /** * @return StatesConfiguration */ public function getConfiguration(): array { return $this->configuration; } /** * @return array */ public function getAliasMap(): array { return $this->aliasMap; } /** * @return array */ public function getComposerNameMap(): array { return $this->composerNameMap; } /** * @return array */ public function getPackages(): array { return $this->packages; } }