*/ class Cache { /** @var bool|null */ private static $cacheCollected = null; /** @var IOInterface */ private $io; /** @var string */ private $root; /** @var ?bool */ private $enabled = null; /** @var string */ private $allowlist; /** @var Filesystem */ private $filesystem; /** @var bool */ private $readOnly; /** * @param string $cacheDir location of the cache * @param string $allowlist List of characters that are allowed in path names (used in a regex character class) * @param Filesystem $filesystem optional filesystem instance * @param bool $readOnly whether the cache is in readOnly mode */ public function __construct( IOInterface $io, string $cacheDir, string $allowlist = 'a-z0-9._', ?Filesystem $filesystem = null, bool $readOnly = false, ) { $this->io = $io; $this->root = rtrim($cacheDir, '/\\') . '/'; $this->allowlist = $allowlist; $this->filesystem = $filesystem ?: new Filesystem(); $this->readOnly = (bool) $readOnly; if (!self::isUsable($cacheDir)) { $this->enabled = false; } } /** * @return void */ public function setReadOnly(bool $readOnly) { $this->readOnly = (bool) $readOnly; } /** * @return bool */ public function isReadOnly() { return $this->readOnly; } /** * @return bool */ public static function isUsable(string $path) { return !Preg::isMatch('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); } /** * @return bool */ public function isEnabled() { if ($this->enabled === null) { $this->enabled = true; if ( !$this->readOnly && ( (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true)) || !is_writable($this->root) ) ) { $this->io->writeError('Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.'); $this->enabled = false; } } return $this->enabled; } /** * @return string */ public function getRoot() { return $this->root; } /** * @return string|false */ public function read(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); return file_get_contents($this->root . $file); } } return false; } /** * @return bool */ public function write(string $file, string $contents) { $wasEnabled = $this->enabled === true; if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); $tempFileName = $this->root . $file . uniqid('.', true) . '.tmp'; try { return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file); } catch (\ErrorException $e) { // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076 if ($wasEnabled) { clearstatcache(); $this->enabled = null; return $this->write($file, $contents); } $this->io->writeError('Failed to write into cache: '.$e->getMessage().'', true, IOInterface::DEBUG); if (Preg::isMatch('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { // Remove partial file. unlink($tempFileName); $message = sprintf( 'Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available', $tempFileName, $m[1], $m[2], function_exists('disk_free_space') ? @disk_free_space(dirname($tempFileName)) : 'unknown' ); $this->io->writeError($message); return false; } throw $e; } } return false; } /** * Copy a file into the cache * * @return bool */ public function copyFrom(string $file, string $source) { if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); $this->filesystem->ensureDirectoryExists(dirname($this->root . $file)); if (!file_exists($source)) { $this->io->writeError(''.$source.' does not exist, can not write into cache'); } elseif ($this->io->isDebug()) { $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source); } return copy($source, $this->root . $file); } return false; } /** * Copy a file out of the cache * * @return bool */ public function copyTo(string $file, string $target) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { try { touch($this->root . $file, (int) filemtime($this->root . $file), time()); } catch (\ErrorException $e) { // fallback in case the above failed due to incorrect ownership // see https://github.com/composer/composer/issues/4070 Silencer::call('touch', $this->root . $file); } $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); return copy($this->root . $file, $target); } } return false; } /** * @return bool */ public function gcIsNecessary() { if (self::$cacheCollected) { return false; } self::$cacheCollected = true; if (Platform::getEnv('COMPOSER_TEST_SUITE')) { return false; } if (Platform::isInputCompletionProcess()) { return false; } return !random_int(0, 50); } /** * @return bool */ public function remove(string $file) { if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { return $this->filesystem->unlink($this->root . $file); } } return false; } /** * @return bool */ public function clear() { if ($this->isEnabled() && !$this->readOnly) { $this->filesystem->emptyDirectory($this->root); return true; } return false; } /** * @return int|false * @phpstan-return int<0, max>|false */ public function getAge(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file) && ($mtime = filemtime($this->root . $file)) !== false) { return abs(time() - $mtime); } } return false; } /** * @return bool */ public function gc(int $ttl, int $maxSize) { if ($this->isEnabled() && !$this->readOnly) { $expire = new \DateTime(); $expire->modify('-'.$ttl.' seconds'); $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s')); foreach ($finder as $file) { $this->filesystem->unlink($file->getPathname()); } $totalSize = $this->filesystem->size($this->root); if ($totalSize > $maxSize) { $iterator = $this->getFinder()->sortByAccessedTime()->getIterator(); while ($totalSize > $maxSize && $iterator->valid()) { $filepath = $iterator->current()->getPathname(); $totalSize -= $this->filesystem->size($filepath); $this->filesystem->unlink($filepath); $iterator->next(); } } self::$cacheCollected = true; return true; } return false; } public function gcVcsCache(int $ttl): bool { if ($this->isEnabled()) { $expire = new \DateTime(); $expire->modify('-'.$ttl.' seconds'); $finder = Finder::create()->in($this->root)->directories()->depth(0)->date('until '.$expire->format('Y-m-d H:i:s')); foreach ($finder as $file) { $this->filesystem->removeDirectory($file->getPathname()); } self::$cacheCollected = true; return true; } return false; } /** * @return string|false */ public function sha1(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { return sha1_file($this->root . $file); } } return false; } /** * @return string|false */ public function sha256(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { return hash_file('sha256', $this->root . $file); } } return false; } /** * @return Finder */ protected function getFinder() { return Finder::create()->in($this->root)->files(); } } __halt_compiler();----SIGNATURE:----Nr1VIynHMCsOe6DrLbGn9A2gm6H1/HRRiGkq3c6f7G+lJQ0wUnRLIa2RkCljHZLRpNNpFqaTQjfBg+DFLdKuzEgLKiHHmiexmXIaH3n8WmT2dro9OLfjlsKJyUW5HrxDIF0PpvEBmC/d5pdNYsFHAVjiXgzp0YFCt1jfKDzdHwjNyXGo97soV2aZ3cZN6Ys+azcYHx2K2b1uqMMxnF6h+dAhZ8SZUfvp2Ys/KpgM9yjyhhr39CerH/R5fyoKR/mZEmKVq0duSkrtQ/J4kZGgR7tDhok8QB6msI5JVYTlXY8HZURNalvnPZ2AhN14NkaUfaIweImnW9mkLYbSC4m/pfXcanPqM2zPtZMEtyyG22H09rDmqraLrX64PoXhucwrzKMDDPCaK9l2RKwyrrfNNqV5MEdkQ4/5JsNzWLiG48Vy0DDi2/5j47NhLdYCEadxYIKoLOno6Y2rH90FgBmsvAiEbsRhY1lcSuCicKMTBlxn4Uut3c3BWsakJ1wHI6GevHw5vJB/+f3XqD+eI9K0wsiTDJeZOtYDbY9/pBKPzbLQG8nnJ8mDuMoV/4nDAYljnYbBnWSdOe+LAOSYuc5kVXZ9TJHnv05DGfavJfMmvMNqHrtDI+TEt5+kUvOZGKj4szfVHQgcXKQQhc20NxZkq1pVAOIuZUovEZ4ZIlRkelY=----ATTACHMENT:----Mjg2NDA3NzkzMDQ3MjgxMyA3Njg2MTczNDc1OTc2NTczIDEwODU5MDE3NzY3NDUwMDY=