diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:46:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:46:43 +0000 |
commit | 3e02d5aff85babc3ffbfcf52313f2108e313aa23 (patch) | |
tree | b01f3923360c20a6a504aff42d45670c58af3ec5 /library/Icinga/Web/FileCache.php | |
parent | Initial commit. (diff) | |
download | icingaweb2-upstream.tar.xz icingaweb2-upstream.zip |
Adding upstream version 2.12.1.upstream/2.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Icinga/Web/FileCache.php')
-rw-r--r-- | library/Icinga/Web/FileCache.php | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/library/Icinga/Web/FileCache.php b/library/Icinga/Web/FileCache.php new file mode 100644 index 0000000..03f0c19 --- /dev/null +++ b/library/Icinga/Web/FileCache.php @@ -0,0 +1,293 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Web; + +class FileCache +{ + /** + * FileCache singleton instances + * + * @var array + */ + protected static $instances = array(); + + /** + * Cache instance base directory + * + * @var string + */ + protected $basedir; + + /** + * Instance name + * + * @var string + */ + protected $name; + + /** + * Whether the cache is enabled + * + * @var bool + */ + protected $enabled = false; + + /** + * The protected constructor creates a new instance with the given name + * + * @param string $name Cache instance name + */ + protected function __construct($name) + { + $this->name = $name; + $tmpDir = sys_get_temp_dir(); + $runtimePath = $tmpDir . '/FileCache_' . $name; + if (is_dir($runtimePath)) { + // Don't combine the following if with the above because else the elseif path will be evaluated if the + // runtime path exists and is not writeable + if (is_writeable($runtimePath)) { + $this->basedir = $runtimePath; + $this->enabled = true; + } + } elseif (is_dir($tmpDir) && is_writeable($tmpDir) && @mkdir($runtimePath, octdec('1750'), true)) { + // Suppress mkdir errors because it may error w/ no such file directory if the systemd private tmp directory + // for the web server has been removed + $this->basedir = $runtimePath; + $this->enabled = true; + } + } + + /** + * Store the given content to the desired file name + * + * @param string $file new (relative) filename + * @param string $content the content to be stored + * + * @return bool whether the file has been stored + */ + public function store($file, $content) + { + if (! $this->enabled) { + return false; + } + + return file_put_contents($this->filename($file), $content); + } + + /** + * Find out whether a given file exists + * + * @param string $file the (relative) filename + * @param int $newerThan optional timestamp to compare against + * + * @return bool whether such file exists + */ + public function has($file, $newerThan = null) + { + if (! $this->enabled) { + return false; + } + + $filename = $this->filename($file); + + if (! file_exists($filename) || ! is_readable($filename)) { + return false; + } + + if ($newerThan === null) { + return true; + } + + $info = stat($filename); + + if ($info === false) { + return false; + } + + return (int) $newerThan < $info['mtime']; + } + + /** + * Get a specific file or false if no such file available + * + * @param string $file the disired file name + * + * @return string|bool Filename content or false + */ + public function get($file) + { + if ($this->has($file)) { + return file_get_contents($this->filename($file)); + } + + return false; + } + + /** + * Send a specific file to the browser (output) + * + * @param string $file the disired file name + * + * @return bool Whether the file has been sent + */ + public function send($file) + { + if ($this->has($file)) { + readfile($this->filename($file)); + + return true; + } + + return false; + } + + /** + * Get absolute filename for a given file + * + * @param string $file the disired file name + * + * @return string absolute filename + */ + protected function filename($file) + { + return $this->basedir . '/' . $file; + } + + /** + * Prepare a sub directory with the given name and return its path + * + * @param string $name + * + * @return string|false Returns FALSE in case the cache is not enabled or an error occurred + */ + public function directory($name) + { + if (! $this->enabled) { + return false; + } + + $path = $this->filename($name); + if (! is_dir($path) && ! @mkdir($path, octdec('1750'), true)) { + return false; + } + + return $path; + } + + /** + * Whether the given ETag matches a cached file + * + * If no ETag is given we'll try to fetch the one from the current + * HTTP request. + * + * @param string $file The cached file you want to check + * @param string $match The ETag to match against + * + * @return string|bool ETag on match, otherwise false + */ + public function etagMatchesCachedFile($file, $match = null) + { + return self::etagMatchesFiles($this->filename($file), $match); + } + + /** + * Create an ETag for the given file + * + * @param string $file The desired cache file + * + * @return string your ETag + */ + public function etagForCachedFile($file) + { + return self::etagForFiles($this->filename($file)); + } + + /** + * Whether the given ETag matchesspecific file(s) on disk + * + * @param string|array $files file(s) to check + * @param string $match ETag to match against + * + * @return string|bool ETag on match, otherwise false + */ + public static function etagMatchesFiles($files, $match = null) + { + if ($match === null) { + $match = isset($_SERVER['HTTP_IF_NONE_MATCH']) + ? trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') + : false; + } + if (! $match) { + return false; + } + + if (preg_match('/([0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{8})-\w+/i', $match, $matches)) { + // Removes compression suffixes as our custom algorithm can't handle compressed cache files anyway + $match = $matches[1]; + } + + $etag = self::etagForFiles($files); + return $match === $etag ? $etag : false; + } + + /** + * Create ETag for the given files + * + * Custom algorithm creating an ETag based on filenames, mtimes + * and file sizes. Supports single files or a list of files. This + * way we are able to create ETags for virtual files depending on + * multiple source files (e.g. compressed JS, CSS). + * + * @param string|array $files Single file or a list of such + * + * @return string The generated ETag + */ + public static function etagForFiles($files) + { + if (is_string($files)) { + $files = array($files); + } + + $sizes = array(); + $mtimes = array(); + + foreach ($files as $file) { + $file = realpath($file); + if ($file !== false && $info = stat($file)) { + $mtimes[] = $info['mtime']; + $sizes[] = $info['size']; + } else { + $mtimes[] = time(); + $sizes[] = 0; + } + } + + return sprintf( + '%s-%s-%s', + hash('crc32', implode('|', $files)), + hash('crc32', implode('|', $sizes)), + hash('crc32', implode('|', $mtimes)) + ); + } + + /** + * Factory creating your cache instance + * + * @param string $name Instance name + * + * @return FileCache + */ + public static function instance($name = 'icingaweb') + { + if ($name !== 'icingaweb') { + $name = 'icingaweb/modules/' . $name; + } + + if (!array_key_exists($name, self::$instances)) { + self::$instances[$name] = new static($name); + } + + return self::$instances[$name]; + } +} |