first commit

This commit is contained in:
2025-08-07 13:15:31 +01:00
commit d903893b4c
21854 changed files with 4461308 additions and 0 deletions

426
inc/cache/simplecache.class.php vendored Normal file
View File

@ -0,0 +1,426 @@
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2020 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/
namespace Glpi\Cache;
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
use Psr\SimpleCache\CacheInterface;
use Laminas\Cache\Psr\SimpleCache\SimpleCacheDecorator;
use Laminas\Cache\Storage\StorageInterface;
class SimpleCache extends SimpleCacheDecorator implements CacheInterface {
/**
* Determines if footprints must be checked.
*
* @var boolean
*/
private $check_footprints;
/**
* Footprint file path, if existing.
*
* @var string|null
*/
private $footprint_file;
/**
* Footprint fallback storage used if footprint file is not available.
*
* @var array
*/
private $footprint_fallback_storage = [];
public function __construct(StorageInterface $storage, $cache_dir, $check_footprints = true) {
parent::__construct($storage);
$this->check_footprints = $check_footprints;
if ($this->check_footprints) {
$this->footprint_file = $cache_dir . '/' . $storage->getOptions()->getNamespace() . '.json';
$this->checkFootprintFileIntegrity();
}
}
public function get($key, $default = null) {
$normalized_key = $this->getNormalizedKey($key);
$cached_value = parent::get($normalized_key, $default);
if (!$this->check_footprints) {
return $cached_value;
}
if ($this->getCachedFootprint($key) !== $this->computeFootprint($cached_value)) {
// If footprint changed, value is no more valid.
return $default;
}
return $cached_value;
}
public function set($key, $value, $ttl = null) {
$normalized_key = $this->getNormalizedKey($key);
if ($this->check_footprints) {
$this->setFootprint($key, $value);
}
return parent::set($normalized_key, $value, $ttl);
}
public function delete($key) {
$normalized_key = $this->getNormalizedKey($key);
if ($this->check_footprints) {
$this->setFootprint($key, null);
}
return parent::delete($normalized_key);
}
public function clear() {
if ($this->check_footprints) {
$this->setAllCachedFootprints([]);
}
return parent::clear();
}
public function getMultiple($keys, $default = null) {
$normalized_keys = array_map([$this, 'getNormalizedKey'], $keys);
$cached_values = parent::getMultiple($normalized_keys, $default);
$footprints = $this->check_footprints ? $this->getMultipleCachedFootprints($keys) : [];
$result = [];
foreach ($keys as $key) {
$normalized_key = $this->getNormalizedKey($key);
$result[$key] = $cached_values[$normalized_key];
if ($this->check_footprints) {
if ($footprints[$key] !== $this->computeFootprint($cached_values[$normalized_key])) {
// If footprint changed, value is no more valid.
$result[$key] = $default;
}
}
}
return $result;
}
public function setMultiple($values, $ttl = null) {
if ($this->check_footprints) {
$this->setMultipleFootprints($values);
}
$values_with_normalized_keys = [];
foreach ($values as $key => $value) {
$normalized_key = $this->getNormalizedKey($key);
$values_with_normalized_keys[$normalized_key] = $value;
}
return parent::setMultiple($values_with_normalized_keys, $ttl);
}
public function deleteMultiple($keys) {
$normalized_keys = array_map([$this, 'getNormalizedKey'], $keys);
if ($this->check_footprints) {
$values = array_combine($keys, array_fill(0, count($keys), null));
$this->setMultipleFootprints($values);
}
return parent::deleteMultiple($normalized_keys);
}
public function has($key) {
$normalized_key = $this->getNormalizedKey($key);
if (!parent::has($normalized_key)) {
return false;
}
if (!$this->check_footprints) {
return true;
}
// Cache value is not usable if stale, consider it has not existing.
return $this->getCachedFootprint($key) === $this->computeFootprint(parent::get($normalized_key));
}
/**
* Returns all known cache keys.
*
* @return array
*/
public function getAllKnownCacheKeys() {
$footprints = $this->getAllCachedFootprints();
return array_keys($footprints);
}
/**
* Returns the computed footprint of a value.
*
* @param mixed $value
*
* @return string
*/
private function computeFootprint($value) {
return sha1(serialize($value));
}
/**
* Returns known footprint for a cached item.
*
* @param string $key
*
* @return string|null
*/
private function getCachedFootprint($key) {
$footprints = $this->getAllCachedFootprints();
return array_key_exists($key, $footprints) ? $footprints[$key] : null;
}
/**
* Return known footprints for multiple cached items.
*
* @param array $keys
*
* @return array
*/
private function getMultipleCachedFootprints(array $keys) {
$footprints = $this->getAllCachedFootprints();
$result = [];
foreach ($keys as $key) {
$result[$key] = $footprints[$key] ?? null;
}
return $result;
}
/**
* Defines footprint for cache item.
*
* @param string $key Key of the cached item.
* @param mixed $values Value of the cached item.
*
* @return void
*/
private function setFootprint($key, $value) {
$this->setMultipleFootprints([$key => $value]);
}
/**
* Defines footprint for multiple cache items.
*
* @param array $values Associative array of cached items, where keys corresponds to the
* cache key of the item and value is its cached value.
*
* @return void
*/
private function setMultipleFootprints(array $values) {
$footprints = $this->getAllCachedFootprints();
foreach ($values as $key => $value) {
$footprints[$key] = $this->computeFootprint($value);
}
$this->setAllCachedFootprints($footprints);
}
/**
* Check footprint file integrity, to ensure that it can be used securely.
*
* @return void
*/
private function checkFootprintFileIntegrity() {
if ((file_exists($this->footprint_file) && !is_writable($this->footprint_file))
|| (!file_exists($this->footprint_file) && !is_writable(dirname($this->footprint_file)))) {
trigger_error(
sprintf('Cannot write "%s" cache footprint file. Cache performance can be lowered.', $this->footprint_file),
E_USER_WARNING
);
$this->footprint_file = null;
return;
}
if (!file_exists($this->footprint_file)) {
// Create empty array in file if not exists.
$this->setAllCachedFootprints([]);
return;
}
// It may potentially happen that a writable file may be unreadable.
if (!is_readable($this->footprint_file)) {
trigger_error(
sprintf('Cannot read "%s" cache footprint file. Cache performance can be lowered.', $this->footprint_file),
E_USER_WARNING
);
$this->footprint_file = null;
return;
}
$file_contents = $this->getFootprintFileContents();
if (empty($file_contents)) {
// Create empty array in file if empty.
$this->setAllCachedFootprints([]);
return;
}
$footprints = json_decode($file_contents, true);
if (json_last_error() !== JSON_ERROR_NONE || !is_array($footprints)) {
// Clear footprint file if not a valid JSON.
trigger_error(
sprintf('Cache footprint file "%s" contents was invalid, it has been cleaned.', $this->footprint_file),
E_USER_WARNING
);
$this->setAllCachedFootprints([]);
}
}
/**
* Get footprint file contents.
*
* @return string|null
*/
private function getFootprintFileContents() {
if (!$handle = fopen($this->footprint_file, 'rb')) {
return null;
}
// Lock the file, if possible (depends on used FS).
// Use a share lock to not make readers wait each oher.
$is_locked = flock($handle, LOCK_SH);
$file_contents = '';
while (!feof($handle)) {
$file_contents .= fread($handle, 8192);
}
if ($is_locked) {
// Unlock the file if it has been locked
flock($handle, LOCK_UN);
}
fclose($handle);
return $file_contents;
}
/**
* Returns all cache footprints.
*
* @return array Associative array of cached items footprints, where keys corresponds to the
* cache key of the item and value is its footprint.
*/
private function getAllCachedFootprints() {
if (null !== $this->footprint_file) {
$file_contents = $this->getFootprintFileContents();
$footprints = !empty($file_contents) ? json_decode($file_contents, true) : null;
if (json_last_error() !== JSON_ERROR_NONE || !is_array($footprints)) {
// Should happen only if file has been corrupted/deleted/truncated after cache instanciation,
// launch integrity tests again to trigger warnings and fix file contents.
$this->checkFootprintFileIntegrity();
return [];
}
return $footprints;
}
return $this->footprint_fallback_storage;
}
/**
* Save all cache footprints.
*
* @param array $footprints Associative array of cached items footprints, where keys corresponds to the
* cache key of the item and value is its footprint.
*
* @return void
*/
private function setAllCachedFootprints($footprints) {
if (null !== $this->footprint_file) {
// Remove null values to prevent storage of deleted footprints
array_filter(
$footprints,
function($val) {
return null !== $val;
}
);
$json = json_encode($footprints, JSON_PRETTY_PRINT);
$handle = fopen($this->footprint_file, 'c');
// Lock the file, if possible (depends on used FS).
// Use an exclusive lock as noone should open the file while it is updated.
$is_locked = flock($handle, LOCK_EX);
$result = ftruncate($handle, 0)
&& fwrite($handle, $json)
&& fflush($handle);
if ($is_locked) {
// Unlock the file if it has been locked
flock($handle, LOCK_UN);
}
fclose($handle);
if ($result !== false) {
return;
} else {
// Should happen only if file is not writable anymore (rights problems or no more disk space),
// fallback to singleton storage.
$this->footprint_file = null;
}
}
$this->footprint_fallback_storage = $footprints;
}
/**
* Returns normalized key to ensure compatibility with cache storage.
*
* @param string $key
*
* @return string
*/
private function getNormalizedKey(string $key): string {
return sha1($key);
}
}